# -*- coding: utf-8 -*-

import json
import logging
import os.path
import subprocess
import time
from base64 import b64encode
from datetime import datetime, timedelta

from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtWidgets import QMessageBox

from comp_cab_setup_modules.config import config_path
from comp_cab_setup_modules.hosts import Hosts
from comp_cab_setup_modules.system import run_command_in_xterm, user, run_command_by_root, \
    run_command, test_ssh, test_ping, check_student_on_host


class PingTest(QThread):
    progress_signal = pyqtSignal(str)
    finish_signal = pyqtSignal()

    def __init__(self, parent=None):
        QThread.__init__(self, parent)
        self.host_list = []

    def run(self):
        cnt = 0
        self.progress_signal.emit("Начало проверки ping<br>")
        fail = []
        for host in self.host_list:
            self.progress_signal.emit(
                f"Проверка {host.name}..."
            )
            if test_ping(host.hostname):
                logging.info(f"{host.name}: проверка ping успешно")
                self.progress_signal.emit("успешно")
                cnt += 1
            else:
                logging.info(f"{host.name}: недоступен")
                self.progress_signal.emit("недоступен")
                fail.append(f'{host.name} ({host.hostname})')
        if cnt == len(self.host_list):
            self.progress_signal.emit(f'<br>Завершение проверки ping.<br>Доступно компьютеров: {cnt} из {cnt}')
        else:
            self.progress_signal.emit(f'<br>Завершение проверки ping.<br>'
                                      f'<font color="red">Доступно компьютеров: {cnt} из {len(self.host_list)}</font>')
            self.progress_signal.emit(f'<br>Недоступные устройства: {", ".join(fail)}')


class SSHRootSetup(QThread):
    progress_signal = pyqtSignal(str)
    finish_signal = pyqtSignal()
    delete_known_hosts_signal = pyqtSignal(bool)

    def __init__(self, parent=None):
        QThread.__init__(self, parent)
        self.host_list = []
        self.fail = []
        self.hosts = Hosts()
        self.replace_keys = True
        self.failed_connection_to_any_host = False

    def run(self):
        self.progress_signal.emit("Начало настройки SSH")
        self.setup_ssh()

    def ping(self) -> list:
        """
        Подключение к хостам и проверка ping
        @return: список хостов в случае успеха, иначе пустой список
        """
        hosts = self.host_list
        if hosts:
            self.progress_signal.emit("Список устройств найден, выполняю ping всех устройств:")
            errors = 0
            list_of_hosts = []
            for host in hosts:
                result = subprocess.run(['ping', '-c1', host.hostname], stdout=subprocess.PIPE)
                if result.returncode == 0:
                    self.progress_signal.emit(f"ping: {host.name}: Успешное соединение")
                    logging.info(f"ping: {host.name}: Успешное соединение {result=}{result.returncode=}")
                    list_of_hosts.append(host.hostname)
                elif result.returncode == 2:
                    logging.info(f"ping: {host.name}: {result=}{result.returncode=}")
                    self.progress_signal.emit(f"ping: {host.name}: Устройство не найдено")
                    errors += 1
                    self.fail.append(f'{host.name} ({host.hostname})')
                else:
                    self.progress_signal.emit(f"ping: {host.name}: неизвестная ошибка")
                    logging.info(host.hostname + f" неизвестная ошибка {result=}{result.returncode=}")
                    errors += 1
                    self.fail.append(f'{host.name} ({host.hostname})')
            self.progress_signal.emit("Завершение настройки ssh.")
            if errors > 0:
                self.progress_signal.emit(f'<font color="red">Успешно: {len(self.host_list) - errors} из '
                                          f'{len(self.host_list)}</font>')
                self.progress_signal.emit(f'<br>Недоступные устройства: {", ".join(self.fail)}')
            else:
                self.progress_signal.emit(f'Успешно: {len(self.host_list)} из {len(self.host_list)}')
            return list_of_hosts
        else:
            self.progress_signal.emit(
                '<br>Заполните список устройств: '
                'перечислите в нём имена компьютеров и запустите скрипт повторно.<br>'
            )
            return []

    def setup_ssh(self) -> None:
        """
        Создание ключей ssh
        Копирование ключей на хосты для пользователя teacher
        Подключение к хостам под пользователем teacher и копирование ключей пользователю root
        """
        list_of_hosts_with_succesfull_ping = self.ping()
        if list_of_hosts_with_succesfull_ping:
            logging.info(f"Начало создания ключа")
            self.progress_signal.emit(f"Создание ключа ssh")
            run_command_in_xterm(f"echo 'Генерация НОВОГО ключа.';"
                                 f"echo 'В случае добавления компьютеров к уже настроенным, выберите n, "
                                 f"чтобы использовать уже созданный ключ!';"
                                 f"ssh-keygen -t ed25519 -q -P '' -f /home/{user}/.ssh/id_ed25519 <<<n")
            logging.info(f"Ключ создан")
            self.progress_signal.emit(f"Ключ ssh создан")
            time.sleep(1)

            logging.info(f"Начало копирования ключей")
            self.progress_signal.emit('Копирую ключ на все доступные компьютеры:')
            cnt = 0
            for host in self.host_list:
                if host.hostname in list_of_hosts_with_succesfull_ping:
                    if host.admin_login == '' or host.admin_pass == '':
                        self.progress_signal.emit(f'Укажите логин и пароль администратора на {host.name}!')
                        self.fail.append(f'{host.name} ({host.hostname})')
                        list_of_hosts_with_succesfull_ping.remove(host.hostname)
                        continue
                    logging.info(f"Копирование открытого ключа")
                    self.progress_signal.emit(f'Копирование открытого ключа на {host.name}...')
                    run_command(
                        f"sshpass -p '{host.admin_pass}' ssh-copy-id {host.admin_login}@{host.hostname}"
                    )
                    cnt += 1
            logging.info(f"Ключи скопированы на {cnt} компьютеров")
            self.progress_signal.emit("Настройка ssh для пользователя из wheel на всех доступных устройствах")
            successful_hostnames = []
            cnt = 0

            for host in self.host_list:
                hostname = host.hostname.strip()
                if hostname in list_of_hosts_with_succesfull_ping:
                    self.progress_signal.emit(f"Попытка подключения к {hostname}")
                    logging.info(f"Попытка подключения к {hostname}")
                    try:
                        result = run_command(f"ssh {host.admin_login}@{host.hostname} 'ls ~/.ssh'")
                        if 'authorized_keys' not in result:
                            self.progress_signal.emit(f"Не найдены ключи или слишком много попыток переподключения "
                                                      f"к {hostname}")
                            logging.info(f"Не найдены ключи или слишком много попыток переподключения к {hostname}")
                            if f'{host.name} ({host.hostname})' not in self.fail:
                                self.fail.append(f'{host.name} ({host.hostname})')
                            self.failed_connection_to_any_host = True
                            continue
                        result = self.ssh_copy_to_root(host)
                        if "ssh-" not in result:
                            self.progress_signal.emit(f"Пароль {host.admin_login} неверный для {hostname}")
                            logging.info(f"Пароль {host.admin_login} неверный для {hostname}")
                            if f'{host.name} ({host.hostname})' not in self.fail:
                                self.fail.append(f'{host.name} ({host.hostname})')
                        else:
                            successful_hostnames.append(host.name)
                            self.progress_signal.emit(f"На {hostname} ssh для root настроен успешно")
                            logging.info(f"На {hostname} ssh для root настроен успешно")
                            cnt += 1
                    except Exception as e:
                        logging.info(f"{e}  ---  Не удалось подключиться к {hostname}")
                        self.progress_signal.emit(f"Не удалось подключиться к {hostname}")
                        if f'{host.name} ({host.hostname})' not in self.fail:
                            self.fail.append(f'{host.name} ({host.hostname})')
                        continue
            for name in successful_hostnames:
                self.hosts.set_true_setup(name)
            if cnt == len(self.host_list):
                self.progress_signal.emit(f'<br>Настройка SSH завершена.<br>Настроено компьютеров: {cnt} из {cnt}')
            else:
                if self.failed_connection_to_any_host:
                    self.delete_known_hosts_signal.emit(True)
                self.progress_signal.emit(f'<br>Настройка SSH завершена.<br><font color="red">Настроено компьютеров: '
                                          f'{cnt} из {len(self.host_list)}</font><br>')
                self.progress_signal.emit(f'<br>Не настроены: {", ".join(self.fail)}')
            if cnt == 0:
                self.progress_signal.emit(f'<br>Внимание: попробуйте перезагрузить этот компьютер '
                                          f'и повторить настройку.')

    def ssh_copy_to_root(self, host) -> str:
        """
        Копирование ключей ssh от teacher в root
        :param host: объект класса Host
        :return: вывод результата от терминала
        """
        logging.info("Начало копирования ключей ssh to root")
        try:
            self.progress_signal.emit(f'Копирование ключей для root на {host.name}...<br>')
            admin_pass_bytes = host.admin_pass.encode('ascii')
            admin_pass_base64_bytes = b64encode(admin_pass_bytes)
            admin_pass_base64_message = admin_pass_base64_bytes.decode('ascii')

            remote_admin_home_folder = run_command(f'ssh {host.admin_login}@{host.hostname} '
                                                   f'xdg-user-dir').strip()
            run_command(f'ssh {host.admin_login}@{host.hostname} '
                        f'"echo {admin_pass_base64_message} | base64 -d | sudo -S mkdir -p /root/.ssh"')
            run_command(f'ssh {host.admin_login}@{host.hostname} '
                        f'"echo {admin_pass_base64_message} | base64 -d | sudo -S touch /root/.ssh/authorized_keys"')
            if self.replace_keys:
                run_command(f'ssh {host.admin_login}@{host.hostname} '
                            f'"echo {admin_pass_base64_message} | base64 -d | sudo -S sh -c '
                            f'\'cat {remote_admin_home_folder}/.ssh/authorized_keys > /root/.ssh/authorized_keys\'"')
            else:
                run_command(f'ssh {host.admin_login}@{host.hostname} '
                            f'"echo {admin_pass_base64_message} | base64 -d | sudo -S sh -c '
                            f'\'cat {remote_admin_home_folder}/.ssh/authorized_keys >> /root/.ssh/authorized_keys\'"')
            result = run_command(f'ssh {host.admin_login}@{host.hostname} '
                                 f'"echo {admin_pass_base64_message} | base64 -d | sudo -S cat '
                                 f'/root/.ssh/authorized_keys"')
            logging.info(f"Ключи /root на {host.name} после копирования: {result}")
            return result
        except Exception as e:
            self.progress_signal.emit(f'Не удалось подключиться по ssh к {host.admin_login} на {host.name}')
            logging.info(f"Не удалось подключиться по ssh к {host.admin_login} на {host.hostname}. Ошибка: {e}")


class SSHDelete(QThread):
    progress_signal = pyqtSignal(str)
    finish_signal = pyqtSignal()

    def __init__(self, parent=None):
        QThread.__init__(self, parent)
        self.host_list = []
        self.hosts = Hosts()

    def run(self):
        self.delete_ssh()

    def delete_ssh(self) -> None:
        """
        Удаляет все ключи ssh с выбранных хостов
        """
        logging.info('Удаление ключей ssh')
        self.progress_signal.emit(f"Удаление ключей ssh, выбрано компьютеров: {len(self.host_list)}")
        cnt = 0
        fail = []
        for host in self.host_list:
            if host.setup is False:
                self.progress_signal.emit(f'На {host.name} не настроен ssh')
                fail.append(f'{host.name} ({host.hostname})')
                continue
            if test_ssh(host):
                self.progress_signal.emit(f'Удаление ключей на {host.name}...')
                logging.info(f'Удаление ключей на {host.name}...')
                remote_admin_home_folder = run_command(f'ssh {host.admin_login}@{host.hostname} '
                                                       f'xdg-user-dir').strip()
                run_command(f'ssh root@{host.hostname} rm -rf /root/.ssh {remote_admin_home_folder}/.ssh')
                self.progress_signal.emit(f'Ключи ssh удалены на {host.name}.')
                self.hosts.set_false_setup(host.name)
                cnt += 1
            else:
                self.progress_signal.emit(f'Компьютер {host.name} недоступен или на нём не настроен ssh.')
                fail.append(f'{host.name} ({host.hostname})')
        self.progress_signal.emit(f'<br>Удаление ключей ssh с компьютеров пользователей завершено.')
        if cnt == len(self.host_list):
            self.progress_signal.emit(f'<br>Успешно обработано компьютеров: {cnt} из {cnt}<br>')
        else:
            self.progress_signal.emit(f'<font color="red">Успешно обработано компьютеров: '
                                      f'{cnt} из {len(self.host_list)}</font>')
            self.progress_signal.emit(f'<br>Не удалось удалить ключи с устройств: {", ".join(fail)}')


class GetIPByMac(QThread):
    progress_signal = pyqtSignal(str)

    def __init__(self, parent=None):
        QThread.__init__(self, parent)
        self.host_list = []
        self.hosts = Hosts()

    def run(self):
        self.get_ip_by_mac()

    def get_ip_by_mac(self) -> None:
        """
        Получает IP по MAC-адресу
        """
        logging.info('Получение IP по MAC-адресу...')
        cnt = 0
        fail = []

        find_ips_by_mac_command_output = run_command('/sbin/arp -a').split('\n')

        for host in self.host_list:
            if host.mac_address is None or host.mac_address == '':
                self.progress_signal.emit(f'У {host.name} не заполнен MAC-адрес')
                logging.info(f'У {host.name} не заполнен MAC-адрес')
                fail.append(f'{host.name} ({host.hostname})')
                continue
            for line in find_ips_by_mac_command_output:
                if host.mac_address.lower() in line.lower():
                    ip_found = line.split('(')[-1].split(')')[0]
                    break
            else:
                self.progress_signal.emit(f'Не удалось определить IP для {host.name}')
                logging.info(f'Не удалось определить IP для {host.name}')
                fail.append(f'{host.name} ({host.hostname})')
                continue
            self.hosts.set_ip(host.name, ip_found)
            cnt += 1
        self.progress_signal.emit(f'<br>Определение IP по MAC-адресам завершено.')
        if cnt == len(self.host_list):
            self.progress_signal.emit(f'<br>Успешно обработано компьютеров: {cnt} из {cnt}<br>')
        else:
            self.progress_signal.emit(f'<font color="red">Успешно обработано компьютеров: '
                                      f'{cnt} из {len(self.host_list)}</font>')
            self.progress_signal.emit(f'<br>Не удалось определить для устройств: {", ".join(fail)}')


# class NetworkFolderSetup(QThread):
#     progress_signal = pyqtSignal(str)
#     finish_signal = pyqtSignal()
#
#     def __init__(self, parent=None):
#         QThread.__init__(self, parent)
#         # self.hosts = None
#         self.host_list = []
#
#     def run(self):
#         self.network_folders()
#
#     def network_folders(self) -> None:
#         """
#         Создание сетевой папки и копирование ярлыка по ssh на хосты
#         """
#         logging.info("Создание сетевой папки")
#         self.progress_signal.emit("Начало настройки сетевых папок")
#         hosts_count = len(self.host_list)
#         success_count = 0
#         self.progress_signal.emit(
#             'Создание сетевой папки share (/home/share) и отправка ссылки на компьютеры учеников'
#         )
#         if smb_conf not in run_command("cat /etc/samba/smb.conf"):
#             logging.info("Установка samba с конфигом")
#             run_command_by_root(
#                 f'dnf makecache && '
#                 f'dnf in -y samba && '
#                 f'mkdir -p /home/share && '
#                 f'chmod 777 /home/share && '
#                 f'echo -e \\"{smb_conf}\\" >> /etc/samba/smb.conf && '
#                 f'service smb restart; '
#                 f'service nmb restart; '
#                 f'systemctl enable smb.service; '
#                 f'systemctl enable nmb.service'
#             )
#         else:
#             logging.info("Установка samba без конфига")
#             run_command_by_root(
#                 f'dnf makecache && '
#                 f'dnf in -y samba && '
#                 f'mkdir -p /home/share && '
#                 f'chmod 777 /home/share && '
#                 f'service smb restart; '
#                 f'service nmb restart; '
#                 f'systemctl enable smb.service; '
#                 f'systemctl enable nmb.service'
#             )
#         current_desktop_folder = run_command('xdg-user-dir DESKTOP')
#         with open(f'{current_desktop_folder}/share.desktop', 'w') as file_link_2:
#             file_link_2.write(network_share_for_teacher)
#         with open(f'{config_path}/share.desktop', 'w') as file_link:
#             file_link.write(network_share.format(teacher_host=this_host))
#         fail = []
#         for host in self.host_list:
#             if test_ssh(host):
#                 if check_student_on_host(host):
#                     current_desktop_folder = run_command(
#                         f'ssh root@{host.hostname} '
#                         f'"sudo -u student xdg-user-dir DESKTOP"').strip().replace(' ', '\\ ')
#                     run_command(
#                         f"sshpass -p {host.student_pass} scp {config_path}/share.desktop "
#                         f"{host.student_login}@{host.hostname}:'{current_desktop_folder}'"
#                     )
#                     # ssh = paramiko.SSHClient()
#                     # ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
#                     # ssh.load_system_host_keys()
#                     # try:
#                     #     ssh.connect(hostname=host.hostname, port=22, timeout=5, username=host.admin_login,
#                     #                 password=host.admin_pass, allow_agent=False, look_for_keys=False)
#                     #     logging.info(f"Подключено по ssh@{host.admin_login} к {host.hostname}")
#                     #     channel: Channel = ssh.invoke_shell()
#                     #     channel_data = str()
#                     #     time.sleep(0.5)
#                     #     # channel.send(f"sudo cat {config_path}/share.desktop | sudo tee /root/.ssh/authorized_keys\n")
#                     #     time.sleep(0.5)
#                     #     channel.send(f"{host.admin_pass}\n")
#                     #     time.sleep(0.5)
#                     #     channel_data += f"{str(channel.recv(99999).decode('utf-8'))}\n"
#                     #     channel.close()
#                     #     ssh.close()
#                     #     logging.info(f"Результат работы paramiko на {host.hostname}: {channel_data}")
#                     self.progress_signal.emit(
#                         f"Компьютер {host.name}: ярлык на сетевую папку скопирован"
#                     )
#                     success_count += 1
#                     logging.info(f'{host.name} ярлык на сетевую папку скопирован')
#                 else:
#                     self.progress_signal.emit(f'На {host.name} отсутствует {host.student_login}')
#                     logging.info(f'На {host.name} отсутствует {host.student_login}')
#                     fail.append(f'{host.name} ({host.hostname})')
#             else:
#                 self.progress_signal.emit(f'{host.name}: не в сети или не настроен ssh')
#                 logging.info(f'{host.name} не в сети или не настроен ssh')
#                 fail.append(f'{host.name} ({host.hostname})')
#         self.progress_signal.emit(
#             f"<br>Копирование ярлыка на сетевую папку завершено.<br>"
#         )
#         if len(self.host_list) == success_count:
#             self.progress_signal.emit(f'Успешно: {success_count} из {success_count} компьютеров')
#         else:
#             self.progress_signal.emit(f'<font color="red">Успешно: {success_count} из '
#                                       f'{len(self.host_list)} компьютеров</font>')
#             self.progress_signal.emit(f'<br>Не скопированы ярлыки на устройства: {", ".join(fail)}')


class VeyonInstall(QThread):
    progress_signal = pyqtSignal(str)
    finish_signal = pyqtSignal()

    def __init__(self, parent=None):
        QThread.__init__(self, parent)
        self.hosts = None
        self.host_list = []
        self.kab = ""

    def run(self):
        self.install_veyon()

    def install_veyon(self) -> None:
        """
        Установка и настройка veyon: создание ключей, установка на учительском компьютере и по ssh на хостах
        """
        self.progress_signal.emit("Начало установки veyon")

        self.progress_signal.emit("Проверка доступности хостов...")
        cnt = 0
        fail = []
        for host in self.host_list:
            self.progress_signal.emit(f"Проверка {host.name}...")
            if not test_ssh(host) or host.setup is False:
                self.progress_signal.emit(f'Компьютер {host.name} недоступен или на нём не настроен ssh.')
                logging.info(f'Компьютер {host.name} недоступен, veyon не будет установлен')
                fail.append(f'{host.name} ({host.hostname})')
                continue
            cnt += 1
        if cnt == len(self.host_list):
            self.progress_signal.emit(f'Успешно: {cnt} из {len(self.host_list)} компьютеров')
        else:
            self.progress_signal.emit(f'<font color="red">Успешно: {cnt} из {len(self.host_list)} компьютеров</font>')

        self.progress_signal.emit("Установка veyon на компьютере учителя...")
        logging.info(f'Установка вейон на компьютере учителя')

        is_veyon_installed = run_command("rpm -q veyon")
        if is_veyon_installed.startswith('veyon'):
            self.progress_signal.emit("На компьютере учителя уже установлен veyon. Настраиваем авторизацию...")
            logging.info(f'На компьютере учителя уже установлен veyon')
            run_command_by_root(
                f"veyon-cli config set Authentication/Method 1; rm -f /tmp/cks*; "
                f"veyon-cli authkeys create {user}; veyon-cli authkeys setaccessgroup {user}/private {user}; "
                f"veyon-cli authkeys export {user}/public /tmp/cks-veyon.pam; "
                f"veyon-cli config export /tmp/cks-veyon.json;"
                f"chown {user}:{user} /tmp/cks-*; "
            )
        else:
            run_command_by_root(
                f"rm -rf /etc/veyon; dnf --refresh -y up ; dnf in veyon -y ; rm -f /tmp/cks*; "
                f"veyon-cli config set Authentication/Method 1; "
                f"veyon-cli authkeys delete {user}/private; veyon-cli authkeys delete {user}/public; "
                f"veyon-cli authkeys create {user}; veyon-cli authkeys setaccessgroup {user}/private {user}; "
                f"veyon-cli authkeys export {user}/public /tmp/cks-veyon.pam; "
                f"veyon-cli config export /tmp/cks-veyon.json;"
                f"chown {user}:{user} /tmp/cks-*; "
            )

            is_veyon_installed = run_command("rpm -q veyon")
            if not is_veyon_installed.startswith('veyon'):
                self.progress_signal.emit("<br>Установка veyon на компьютере учителя не удалась")
                logging.info(f'Установка вейон на комьютере учителя не удалась')
                return

            self.progress_signal.emit("<br>Установка veyon на компьютере учителя завершена")
            logging.info(f'Установка вейон на компьютере учителя завершена успешно')

        self.progress_signal.emit("Создание ярлыка на рабочем столе...")
        current_desktop_folder = run_command('xdg-user-dir DESKTOP').strip()
        if run_command(
                f'ln -sf /usr/share/applications/veyon-master.desktop "{current_desktop_folder}/veyon-master.desktop'
                f'" && echo $?').endswith('0'):
            self.progress_signal.emit("Ярлык на рабочем столе создан.")

        logging.info(f'Установка вейон на компьютерах учеников')
        self.progress_signal.emit("<br>Установка Veyon на компьютеры учеников...")
        cnt = 0
        for host in self.host_list:
            if not test_ssh(host):
                self.progress_signal.emit(f'Компьютер {host.name} недоступен или на нём не настроен ssh.')
                logging.info(f'Компьютер {host.name} недоступен, veyon не будет установлен')
                if f'{host.name} ({host.hostname})' not in fail:
                    fail.append(f'{host.name} ({host.hostname})')
                continue
            if host.setup is True:
                self.progress_signal.emit(f'Установка Veyon на {host.name}...')
                self.progress_signal.emit(f'Начало обновления системы на {host.name}... '
                                          f'Внимание: это может занять много времени')
                run_command(f'ssh root@{host.hostname} "dnf --refresh -y up && dnf in -y veyon-core && '
                            f'veyon-cli config set Authentication/Method 1"')
                is_veyon_installed = run_command(f'ssh root@{host.hostname} "rpm -q veyon-core"').startswith('veyon'
                                                                                                             '-core')
                if not is_veyon_installed:
                    self.progress_signal.emit(
                        f'На {host.name} установка veyon не удалась по неизвестной причине.')
                    logging.info(f'На {host.name} установка veyon не удалась по неизвестной причине.')
                    if f'{host.name} ({host.hostname})' not in fail:
                        fail.append(f'{host.name} ({host.hostname})')
                    continue

                cnt += 1
            else:
                self.progress_signal.emit(f'На {host.name} не настроены ключи ssh')
                logging.info(f'На {host.name} не настроены ключи ssh')
                if f'{host.name} ({host.hostname})' not in fail:
                    fail.append(f'{host.name} ({host.hostname})')
        self.progress_signal.emit(f'<br>Установка Veyon завершена.<br>')
        if cnt == len(self.host_list):
            self.progress_signal.emit(f'Успешно: {cnt} из {len(self.host_list)} компьютеров')
        else:
            self.progress_signal.emit(f'<font color="red">Успешно: {cnt} из {len(self.host_list)} компьютеров</font>')
            fail = list(set(fail))
            self.progress_signal.emit(f'<br>Не удалась установка veyon на устройствах: {", ".join(fail)}')
        logging.info('Завершение установки Veyon')


class VeyonSetup(QThread):
    progress_signal = pyqtSignal(str)
    finish_signal = pyqtSignal(str)
    computer_name_conflict_signal = pyqtSignal(list)
    cabinet_conflict_signal = pyqtSignal(str)

    def __init__(self, parent=None):
        QThread.__init__(self, parent)
        self.hosts = None
        self.host_list = []
        self.kab = ""
        self.conflict_cabinet_button = None
        self.conflict_comp_button = None

    def run(self):
        self.setup_veyon()

    def setup_veyon(self) -> None:
        """
        Создание и настройка кабинета veyon, настройка veyon на хостах
        @return: None
        """
        is_veyon_installed = run_command('rpm -q veyon').startswith('veyon')
        if not is_veyon_installed:
            logging.info('Veyon не установлен на компьютере учителя.')
            self.progress_signal.emit('Veyon не установлен на компьютере учителя. Отмена настройки.')
            self.terminate()

        network_objects = ''
        is_kab_exists = False
        kab_id = ''

        try:
            veyon_cli_data = json.loads(run_command('veyon-cli config get BuiltinDirectory/NetworkObjects'))
            for place in veyon_cli_data:
                if place['Type'] == 2 and place['Name'] == self.kab:
                    is_kab_exists = True
                    kab_id = place['Uid'][1:-1]
                    break
        except Exception as e:
            veyon_cli_data = dict()
            logging.info(f'Не найдено ни одного сетевого места. Создан пустой словарь. Ошибка: {e}')

        logging.info(f'Указан номер кабинета {self.kab}\nis_kab_exists={is_kab_exists}')
        network_places = ''
        cabinet_recreated = False
        if is_kab_exists:
            self.cabinet_conflict_signal.emit(self.kab)

            button = self.conflict_cabinet_button

            if button == QMessageBox.Yes:
                network_places = f"veyon-cli networkobjects remove {kab_id} && " \
                                 f"veyon-cli networkobjects add location \\\"{self.kab}\\\" && "
                self.progress_signal.emit(f"<br>Кабинет {self.kab} будет удалён и создан заново")
                logging.info(f"Кабинет {self.kab} удалён и создан заново")
                cabinet_recreated = True
            elif button == QMessageBox.Cancel:
                self.progress_signal.emit('Настройка отменена пользователем.')
                self.terminate()
                self.wait()

        else:
            network_places = f"veyon-cli networkobjects add location \\\"{self.kab}\\\" && "
            self.progress_signal.emit(f"<br>Кабинет {self.kab} будет создан")
            logging.info(f"Кабинет {self.kab} создан")

        cnt, fail = 0, []
        cancelled_hosts = []
        button = QMessageBox.Yes
        for host in self.host_list:

            mac_address = "aa:bb:cc:dd:ee:ff" if not host.mac_address else host.mac_address
            name = host.name.replace(' ', '_')

            for place in veyon_cli_data:
                # Если нашёлся такой уже настроенный компьютер
                if place['Type'] == 3 and place['Name'] == host.name and 'ParentUid' in place and \
                        place['ParentUid'][1:-1] == kab_id:
                    # Если не была отменена настройка кабинета или не было выбрано универсальное действие
                    if button not in (
                            QMessageBox.Cancel,
                            QMessageBox.YesAll,
                            QMessageBox.NoAll,
                            QMessageBox.Ignore
                    ) and not cabinet_recreated:
                        self.computer_name_conflict_signal.emit([name, self.kab, host.hostname, host.mac_address])
                        button = self.conflict_comp_button

                    if button in (QMessageBox.Yes, QMessageBox.YesAll):
                        self.progress_signal.emit(f'Удаляем ключи veyon на {host.name}...')
                        # run_command(f'ssh root@{host.hostname} "veyon-cli authkeys delete {user}/public"')
                        run_command(f'ssh root@{host.hostname} "veyon-cli authkeys delete '
                                    f'{user}/public"')
                        network_objects += f'veyon-cli networkobjects remove {place["Uid"]} ; '
                        network_objects += f'veyon-cli networkobjects add ' \
                                           f'computer "{name}" "{host.hostname}" "{mac_address}" "{self.kab}"; '
                    elif button in (QMessageBox.No, QMessageBox.NoAll):
                        self.progress_signal.emit(f'Отмена настройки veyon на {host.name}.')
                        fail.append(f'{host.name} ({host.hostname})')
                        cancelled_hosts.append(host)
                    elif button in (QMessageBox.Ok, QMessageBox.Ignore):
                        self.progress_signal.emit(f'Добавляем {host.name} в конфигурацию...')
                        network_objects += f'veyon-cli networkobjects add ' \
                                           f'computer "{name}" "{host.hostname}" "{mac_address}" "{self.kab}"; '
                    elif button == QMessageBox.Cancel:
                        self.progress_signal.emit(f'Отмена настройки veyon')
                        self.terminate()
                        self.wait()
                    else:
                        self.progress_signal.emit(f'Произошла неизвестная ошибка')
                        logging.info(f'Произошла неизвестная ошибка')
                        self.terminate()
                        self.wait()
                    break

            # Если компьютер не настроен, удалить возможно оставшиеся ключи
            else:
                # run_command(f'ssh root@{host.hostname} "veyon-cli authkeys delete {user}/public"')
                network_objects += f'veyon-cli networkobjects add computer ' \
                                   f'\\\"{name}\\\" \\\"{host.hostname}\\\" \\\"{mac_address}\\\" \\\"{self.kab}\\\"; '

        existing_keys = run_command('veyon-cli authkeys list').split('\n')

        create_veyon_keys_command = f'chown :wheel /etc/veyon/keys/private/{user}/key && '
        if f'{user}/private' not in existing_keys or f'{user}/public' not in existing_keys:
            create_veyon_keys_command = f'veyon-cli authkeys create {user} && ' \
                                        f'chown :wheel /etc/veyon/keys/private/{user}/key && '

        export_keys_command = ''
        if not os.path.isfile('/tmp/cks-veyon.pam') or not os.path.isfile('/tmp/cks-veyon.json'):
            logging.info('При настройке veyon на компьютере учителя не обнаружены экспортированные ключи.'
                         'Экспортируем в /tmp')
        else:
            logging.info('Были найдены экспортированные ключи, удаляем...')
            if os.path.isfile('/tmp/cks-veyon.pam') or os.path.isfile('/tmp/cks-veyon.json'):
                export_keys_command = 'rm -rf /tmp/cks-veyon.* && '
        export_keys_command += (f"veyon-cli authkeys export {user}/public /tmp/cks-veyon.pam; "
                                f"veyon-cli config export /tmp/cks-veyon.json; ")

        if len(self.host_list) != len(cancelled_hosts):
            result = run_command_by_root(
                f"{create_veyon_keys_command}"
                f"{network_places}"
                f"{network_objects}"
                f"{export_keys_command}"
                f"veyon-cli service start; systemctl restart veyon.service; echo 830"
            )
            if not result.endswith('830'):
                self.progress_signal.emit(f'Настройка veyon на учительском компьютере не удалась. '
                                          f'Проверьте правильность пароля root.')
                logging.info('При настройке veyon введён неправильный пароль root')
                return

            self.progress_signal.emit(f"Команды для настройки Veyon отправлены на компьютеры учеников.\n"
                                      f"Дождитесь перезагрузки устройств.")

        veyon_cli_data = json.loads(run_command('veyon-cli config get BuiltinDirectory/NetworkObjects'))
        is_kab_exists = False
        for place in veyon_cli_data:
            if place['Type'] == 2 and place['Name'] == self.kab:
                is_kab_exists = True
        if is_kab_exists:
            self.progress_signal.emit(f'На учительском компьютере настроен кабинет {self.kab}.')
        else:
            self.progress_signal.emit(f'Настройка veyon на учительском компьютере не удалась.')
            return
        self.progress_signal.emit(f'Настройка veyon на компьютерах учеников...')

        for host in self.host_list:
            if host in cancelled_hosts:
                self.progress_signal.emit(f'На {host.name} настройка veyon была отменена пользователем.')
                continue
            if not test_ssh(host):
                self.progress_signal.emit(f'Не удалось подключиться к {host.name}, проверьте настройку ssh.')
                fail.append(f'{host.name} ({host.hostname})')
                continue
            if not run_command(f'ssh root@{host.hostname} rpm -q veyon-core').startswith('veyon-core'):
                self.progress_signal.emit(f'На {host.name} не установлен veyon.')
                fail.append(f'{host.name} ({host.hostname})')
                continue

            run_command(f'scp /tmp/cks-veyon.pam root@{host.hostname}:/tmp/')
            run_command(f'scp /tmp/cks-veyon.json root@{host.hostname}:/tmp/')
            self.progress_signal.emit(f'Ключи скопированы. Настройка {host.name}...')
            run_command(f'ssh root@{host.hostname} "veyon-cli authkeys delete {user}/public"')
            run_command(f'ssh root@{host.hostname} "veyon-cli authkeys import {user}/public '
                        f'/tmp/cks-veyon.pam"')
            run_command(f'ssh root@{host.hostname} "veyon-cli config import /tmp/cks-veyon.json"')
            run_command(f'ssh root@{host.hostname} "veyon-cli config set Service/HideTrayIcon true"')
            run_command(f'ssh root@{host.hostname} "sleep 1"')
            run_command(f'ssh root@{host.hostname} reboot')
            run_command(f'ssh root@{host.hostname} "sleep 1"')
            logging.info(f"Veyon установлен и настроен на {host.name}")
            self.progress_signal.emit(f"Veyon установлен и настроен на {host.name}<br>"
                                      f"На {host.name} отправлена команда перезагрузки")
            cnt += 1
        self.finish_signal.emit('Настройка Veyon завершена.')
        if cnt == len(self.host_list):
            self.progress_signal.emit(f'Успешно: {cnt} из {len(self.host_list)} компьютеров')
        else:
            self.progress_signal.emit(f'<font color="red">Успешно: {cnt} из {len(self.host_list)} компьютеров</font>')
            fail = list(set(fail))
            self.progress_signal.emit(f'<br>Не удалась установка veyon на устройствах: {", ".join(fail)}')
        logging.info('Завершение установки Veyon')


class RestartVeyonOnHosts(QThread):
    start_signal = pyqtSignal(str)
    progress_signal = pyqtSignal(str)
    finish_signal = pyqtSignal(str)

    def __init__(self):
        super().__init__()
        self.host_list = []

    def run(self) -> None:
        """
        Перезапуск службы Veyon на хостах
        """
        hosts_count = len(self.host_list)
        success_count = 0
        fail = []
        self.start_signal.emit(f"Перезапуск службы Veyon на {hosts_count} компьютеров...")
        for host in self.host_list:
            if not test_ssh(host):
                self.progress_signal.emit(f'На {host.name} не настроен ssh.')
                fail.append(f'{host.name} ({host.hostname})')
                continue
            self.progress_signal.emit(f'Перезапуск службы Veyon на {host.name}...')

            res = run_command(f'ssh root@{host.hostname} "systemctl restart veyon.service; echo $?"')
            if res.strip() == '0':
                success_count += 1
                self.progress_signal.emit('Успешно.')
            else:
                self.progress_signal.emit(f'Не удалось перезапустить сервис Veyon на {host.name}.')
                fail.append(f'{host.name} ({host.hostname})')
        self.finish_signal.emit(f"Перезапуск сервиса Veyon завершён.")
        if success_count == len(self.host_list):
            self.finish_signal.emit(f'Успешно: {success_count} из {success_count} компьютеров.')
        else:
            self.finish_signal.emit(f'<font color="red">Успешно: {success_count} из {len(self.host_list)} '
                                    f'компьютеров.</font>')
            self.finish_signal.emit(f"Не удалось перезапустить сервис "
                                    f"на компьютерах {', '.join(fail)}")


class SSHCommandInThreads(QThread):
    start_signal = pyqtSignal(str)
    progress_signal = pyqtSignal(str)

    def __init__(self, host_list: list, command, run_from_root: bool):
        super().__init__()
        self.host_list = host_list
        self.command = command
        self.run_from_root = run_from_root

    def run(self) -> None:
        self.run_command_on_ssh_from_str()

    def run_command_on_ssh_from_str(self) -> None:
        """
        Выполнение команды от админа или рута по ssh
        """
        cnt, fail = 0, []
        for host in self.host_list:
            logging.info(f"Выполнение команды {self.command} "
                         f"{'от root ' if self.run_from_root else ''}на {host.name}")
            self.progress_signal.emit(f"Выполнение команды {self.command} {'от root ' if self.run_from_root else ''}"
                                      f"на {host.name}...")
            if not test_ssh(host):
                self.progress_signal.emit(f'Компьютер {host.name} недоступен или на нём не настроен ssh.')
                logging.info(f'Компьютер {host.name} недоступен, команда {self.command} не будет выполнена.')
                if f'{host.name} ({host.hostname})' not in fail:
                    fail.append(f'{host.name} ({host.hostname})')
                continue
            # client = SSHClient()
            # client.load_system_host_keys()
            if not self.run_from_root and not host.admin_login:
                self.progress_signal.emit(f"Укажите логин администратора на {host.name}")
                fail.append(f'{host.name} ({host.hostname})')
                continue
            try:
                if 'bash -s' not in self.command and \
                        not (self.command.strip().startswith('"') and self.command.strip().endswith('"')):
                    self.command = f'"{self.command}"'
                result = run_command(f'ssh {"root" if self.run_from_root else host.admin_login}@{host.hostname} '
                                     f'{self.command}')
                self.progress_signal.emit(f"Результат выполнения на {host.name}:<br>{result}")
                logging.info(f"\nРезультат выполнения на {host.name}:\n{result}")
                cnt += 1
            except Exception as e:
                self.progress_signal.emit(
                    f'Не удалось подключиться по ssh к {host.admin_login if not self.run_from_root else "root"}'
                    f' на {host.name}')
                logging.info(f"\nНе удалось подключиться по ssh без пароля к "
                             f"{host.admin_login if not self.run_from_root else 'root'} на {host.name}, "
                             f"возникла ошибка {e}")
                if f'{host.name} ({host.hostname})' not in fail:
                    fail.append(f'{host.name} ({host.hostname})')
        self.progress_signal.emit(
            f"<br>Выполнение команды {self.command.replace('<', '&lt;').replace('>', '&gt;')} завершено.<br>")
        if cnt == len(self.host_list):
            self.progress_signal.emit(f'Выполнено на {cnt} из {cnt} компьютеров.')
        else:
            self.progress_signal.emit(f'<font color="red">Выполнено на '
                                      f'{cnt} из {len(self.host_list)} компьютеров.</font>')
            self.progress_signal.emit(f'<br>Не удалось выполнить на устройствах: {", ".join(fail)}')

    # def run_command_on_ssh_from_list(self, commands_list: list) -> None:
    #     """
    #     Выполнение списка команд от админа или рута по ssh (не используется)
    #     @param commands_list: список команд
    #     """
    #     cnt, fail = 0, []
    #     logging.info(f"Выполнение команды {commands_list} на {self.host.hostname}")
    #     client = SSHClient()
    #     client.load_system_host_keys()
    #     for host in self.host_list:
    #         try:
    #             client.connect(hostname=host.hostname, username=host.admin_login, timeout=2)
    #             for command in commands_list:
    #                 stdin, stdout, stderr = client.exec_command(command)
    #                 result = stdout.read().decode().strip()
    #                 self.progress_signal.signal.emit(
    #                     f"Результат выполнения {command} на {host.name}:<br>{result}"
    #                 )
    #                 logging.info(
    #                     f"\nРезультат выполнения {command} на {host.name}:\n{result}"
    #                 )
    #                 cnt += 1
    #         except (AuthenticationException, SSHException, socket.gaierror):
    #             self.progress_signal.signal.emit(
    #                 f'Не удалось подключиться по ssh к {host.admin_login} на {host.name}')
    #             logging.info(f"Не удалось подключиться по ssh без пароля к {host.admin_login} на {host.name}")
    #             fail.append(f'{host.name} ({host.hostname})')
    #         except Exception as e:
    #             self.progress_signal.signal.emit(f'{host.name} неизвестная ошибка.')
    #             logging.info(f"неизвестная ошибка {host.name}: {e}")
    #             fail.append(f'{host.name} ({host.hostname})')
    #         finally:
    #             client.close()
    #     self.progress_signal.emit(f"<br>Выполнение команд {self.command} завершено.<br>")
    #     if cnt == len(self.host_list):
    #         self.progress_signal.emit(f'Выполнено на {cnt} из {cnt} компьютеров.')
    #     else:
    #         self.progress_signal.emit(f'<font color="red">Выполнено на '
    #                                   f'{cnt} из {len(self.host_list)} компьютеров.</font>')
    #         self.progress_signal.emit(f'<br>Не удалось выполнить на устройствах: {", ".join(fail)}')


def get_current_user_on_x(host) -> str:
    """
    Получение пользователя с графической сессией на хосте
    @param host: объект Host
    @return: строка с именем пользователя. Если ни у одного пользователя не запущена X-сессия, возвращает логин ученика
    """
    try:
        loginctl_lines = run_command(f'ssh root@{host.hostname} loginctl').split('\n')
        tty_session_ids = dict()
        for line in loginctl_lines[1:]:
            if len(line.strip().split()) > 0 and 'tty' in line.strip().split()[-1] and \
                    int(line.strip().split()[1]) >= 500:
                tty_session_ids[line.strip().split()[0]] = line.strip().split()[2]
        for session_id in tty_session_ids.keys():
            session_status = run_command(f'ssh root@{host.hostname} '
                                         f'loginctl show-session --property Active --value {session_id}').strip()
            if session_status == 'yes':
                return tty_session_ids[session_id]
    except Exception as e:
        logging.info(f'Не удалось определить текущего пользователя на {host.name}: {e}')
        logging.info(f'Установлен указанный в таблице {host.student_login}')
        return host.student_login
    logging.info(f'На {host.name} нет активных сессий. Вывод loginctl: {loginctl_lines}')
    logging.info(f'Установлен логин, указанный в таблице: {host.student_login}')
    return host.student_login


def get_remote_desktop_folder(host, current_login_on_host: str) -> str:
    """
    Возвращает адрес домашней папки на хосте
    @param host: объект Host
    @param current_login_on_host: логин пользователя на хосте
    @return: строка с адресом домашней папки
    """
    if current_login_on_host != 'guest':
        remote_desktop_folder = run_command(f'ssh root@{host.hostname} '
                                            f'"sudo -u {current_login_on_host} '
                                            f'xdg-user-dir DESKTOP"').strip().replace(' ', '\\ ')
    else:
        try:
            remote_desktop_folder = ''
            run_command('rm -rf /tmp/guest_folders')
            run_command(f'scp -r root@{host.hostname}:/home/guest/.config/user-dirs.dirs '
                        f'/tmp/guest_folders')
            with open('/tmp/guest_folders', 'r') as inp:
                lines = inp.readlines()
                for line in lines:
                    if line.strip().startswith('XDG_DESKTOP_DIR'):
                        remote_desktop_folder = line.split('"')[1]
                        break

            remote_home_folder = ''
            guest_passwd_output = run_command(f'ssh root@{host.hostname} '
                                              f'"cat /etc/passwd | grep guest"').split('\n')
            for line in guest_passwd_output:
                if len(line.strip().split(':')) > 0 and line.strip().split(':')[0] == 'guest':
                    remote_home_folder = line.strip().split(':')[5]

            if not remote_home_folder:
                remote_home_folder = '/home/guest'
            if not remote_desktop_folder:
                remote_desktop_folder = '/home/guest/Рабочий стол'
            remote_desktop_folder = remote_desktop_folder.replace('$HOME', remote_home_folder).replace(' ', '\\ ')
        except Exception as e:
            logging.info(f'Не удалось получить адрес Рабочего стола гостя на {host.name}.'
                         f'Установлено значение /home/guest/Рабочий стол. Исключение: {e}')
            remote_desktop_folder = '/home/guest/Рабочий стол'.replace(' ', '\\ ')
    return remote_desktop_folder.strip()


class GetWorks(QThread):
    start_signal = pyqtSignal(str)
    progress_signal = pyqtSignal(str)
    finish_signal = pyqtSignal(str)

    def __init__(self, parent=None):
        QThread.__init__(self, parent)
        self.hosts_list = None
        self.date = None
        self.text = None
        self.user_folder = True
        self.hosts = Hosts()

    def run(self) -> None:
        """
        Копирует работы из папки Сдать работы с Рабочего стола учеников в папку Работы на Рабочем столе учителя, где
        создаются подпапки с указанным именем, а также текущей датой и временем в названии (опционально)
        @return: None
        """

        hosts_count = len(self.hosts_list)
        success_count = 0
        if len(self.hosts_list) == 0:
            self.progress_signal.emit("Не выбрано ни одного компьютера")
            return
        self.start_signal.emit(
            f"Выбрано компьютеров: {hosts_count}<br>Сбор работ начинается<br>"
        )
        fail = []
        self.text = self.text.replace(' ', '\\ ')
        for host in self.hosts_list:
            if test_ssh(host):
                current_login_on_host = get_current_user_on_x(host)
                if current_login_on_host == host.student_login and not check_student_on_host(host):
                    self.progress_signal.emit(f'На {host.name} нет учётной записи {host.student_login}.')
                    fail.append(f'{host.name} ({host.hostname})')
                    continue

                remote_desktop_folder = get_remote_desktop_folder(host, current_login_on_host)

                remote_desktop = run_command(
                    f'ssh root@{host.hostname} ls "{remote_desktop_folder}"')
                if 'Сдать работы' not in remote_desktop.split('\n'):
                    self.progress_signal.emit(f'На рабочем столе ученика {current_login_on_host} '
                                              f'на хосте {host.name} нет папки Сдать работы. Создаём...')
                    run_command(f'ssh root@{host.hostname} '
                                f'"mkdir -p {remote_desktop_folder}/Сдать\ работы"')
                    run_command(f'ssh root@{host.hostname} '
                                f'chown {current_login_on_host} "{remote_desktop_folder}/Сдать\ работы"')
                    logging.info(f'На рабочем столе ученика {current_login_on_host} на хосте {host.name} '
                                 f'нет папки Сдать работы, она была создана')
                    fail.append(f'{host.name} ({host.hostname})')
                    continue
                current_desktop_folder = run_command('xdg-user-dir DESKTOP').strip()

                folder_to_get_works_to = f'{current_desktop_folder}/Работы/'
                if not run_command(f'py-ini-config get {config_path}/ccs.conf Main NoDateInGetWorks').strip() == 'true':
                    folder_to_get_works_to += f'{self.date}/'
                if self.user_folder:
                    folder_to_get_works_to += f'{self.text}/'
                run_command(
                    f'mkdir -p "{folder_to_get_works_to}{host.name}"'
                )

                run_command(
                    f'scp -r root@{host.hostname}:\'{remote_desktop_folder}/Сдать\ работы/*\' '
                    f'\"{folder_to_get_works_to}{host.name}\"'
                )

                self.progress_signal.emit(f'{host.name}: работы ученика {current_login_on_host} сохранены успешно')
                logging.info(f'{host.name} работы ученика {current_login_on_host} сохранены успешно')
                success_count += 1
            else:
                self.progress_signal.emit(f'{host.name}: не в сети или не настроен ssh')
                logging.info(f'{host.name} не в сети или не настроен ssh')
                fail.append(f'{host.name} ({host.hostname})')
        if success_count == 0:
            self.finish_signal.emit(f"<br>Сбор работ не выполнен.")
        elif success_count < hosts_count:
            self.finish_signal.emit(
                f'Сбор работ завершился.<br>'
                f'<font color="red">Собраны работы с {success_count} из {hosts_count} компьютеров</font>')
            self.finish_signal.emit(f'<br>Не собраны работы с устройств: {", ".join(fail)}')
        else:
            self.finish_signal.emit(
                f'Сбор работ завершён.<br>'
                f'Собраны работы с {success_count} из {hosts_count} компьютеров')


class CleanWorks(QThread):
    start_signal = pyqtSignal(str)
    progress_signal = pyqtSignal(str)
    finish_signal = pyqtSignal(str)

    def __init__(self, parent=None):
        QThread.__init__(self, parent)
        self.hosts_list = None
        self.hosts = Hosts()

    def run(self) -> None:
        """
        Очищает папку Сдать работы на Рабочем столе учеников на выбранных компьютерах
        """
        hosts_count = len(self.hosts_list)
        success_count = 0
        self.start_signal.emit(
            f"Выбрано компьютеров: {hosts_count}<br>Очистка директорий для сбора работ начинается<br>"
        )
        fail = []
        for host in self.hosts_list:
            if test_ssh(host):
                current_login_on_host = get_current_user_on_x(host)
                if current_login_on_host == host.student_login and not check_student_on_host(host):
                    self.progress_signal.emit(f'На {host.name} нет учётной записи {host.student_login}.')
                    fail.append(f'{host.name} ({host.hostname})')
                    continue

                remote_desktop_folder = get_remote_desktop_folder(host, current_login_on_host)

                remote_desktop_files = run_command(
                    f'ssh root@{host.hostname} ls "{remote_desktop_folder}"')
                if 'Сдать работы' not in remote_desktop_files.split('\n'):
                    self.progress_signal.emit(f'На рабочем столе ученика {current_login_on_host} на хосте {host.name} '
                                              f'нет папки Сдать работы. Создаём...')
                    logging.info(f'На рабочем столе ученика {current_login_on_host} на хосте {host.name} '
                                 f'нет папки Сдать работы')
                    run_command(f'ssh root@{host.hostname} mkdir -p "{remote_desktop_folder}/Сдать\ работы"')
                    run_command(f'ssh root@{host.hostname} '
                                f'chown {current_login_on_host} "{remote_desktop_folder}/Сдать\ работы"')
                    fail.append(f'{host.name} ({host.hostname})')
                    continue
                run_command(
                    f'ssh root@{host.hostname} \'rm -rf {remote_desktop_folder}/Сдать\ работы/*\'')
                self.progress_signal.emit(f'{host.name}: очистка завершена')
                logging.info(f'{host.name} очистка завершена')
                success_count += 1
            else:
                self.progress_signal.emit(f'{host.name}: не в сети или не настроен ssh')
                logging.info(f'{host.name} не в сети или не настроен ssh')
                fail.append(f'{host.name} ({host.hostname})')
        if success_count == 0:
            self.finish_signal.emit(f"<br>Очистка директорий для сбора работ не выполнена.")
        elif success_count < hosts_count:
            self.finish_signal.emit(
                f'<br>Очистка директорий для сбора работ завершилась.<br>'
                f'<font color="red">Очищено {success_count} из {hosts_count}</font>')
            self.finish_signal.emit(f'<br>Не очищены папки на устройствах: {", ".join(fail)}')
        else:
            self.finish_signal.emit(
                f'Очистка директорий завершена.<br>'
                f'Очищены директории на {success_count} из {hosts_count} компьютеров')


class СreateStudent(QThread):
    start_signal = pyqtSignal(str)
    progress_signal = pyqtSignal(str)
    finish_signal = pyqtSignal(str)

    def __init__(self):
        super().__init__()
        self.host_list = None
        # self.student_pass = None
        self.hosts = Hosts()

    def run(self) -> None:
        """
        Создаёт учётки учеников на выбранных хостах
        """
        hosts_count = len(self.host_list)
        success_count = 0
        self.start_signal.emit(
            f"Выбрано компьютеров: {hosts_count}<br>Создание учётной записи ученика начинается<br>"
        )
        fail = []
        for host in self.host_list:
            if test_ssh(host):

                if check_student_on_host(host):
                    self.progress_signal.emit(f'На {host.name} уже существует {host.student_login}')
                    logging.info(f'На {host.name} уже существует {host.student_login}')
                    fail.append(f'{host.name} ({host.hostname})')
                else:
                    student_pass_bytes = host.student_pass.encode('ascii')
                    student_pass_base64_bytes = b64encode(student_pass_bytes)
                    student_pass_base64_message = student_pass_base64_bytes.decode('ascii')
                    command = f'echo "useradd {host.student_login} && ' \
                              f'rm -rf studentpass.txt && ' \
                              f'echo {host.student_login}:\$(echo {student_pass_base64_message} | base64 -d)' \
                              f' > studentpass.txt && ' \
                              f'chpasswd < studentpass.txt && rm -rf studentpass.txt && reboot" | at now'

                    run_command(f'ssh root@{host.hostname} \'{command}\'')
                    self.progress_signal.emit(f'На {host.name} создан {host.student_login}')
                    logging.info(f'На {host.name} создан {host.student_login}')
                    success_count += 1
            else:
                self.progress_signal.emit(f'{host.name}: не в сети или не настроен ssh')
                logging.info(f'{host.name} не в сети или не настроен ssh')
                fail.append(f'{host.name} ({host.hostname})')
        if success_count == 0:
            self.progress_signal.emit(f"<br>Cоздание учётных записей учеников не выполнено.")
        elif success_count < hosts_count:
            self.progress_signal.emit(
                f'<br>Cоздание учётных записей учеников завершено. Компьютеры будут перезагружены.<br>'
                f'<font color="red">Выполнено на {success_count} из {hosts_count} устройств.</font>')
            self.progress_signal.emit(f'<br>Не выполнено на устройствах: {", ".join(fail)}')
        else:
            self.progress_signal.emit(
                f'Cоздание учётных записей учеников завершено. Компьютеры будут перезагружены.<br>'
                f'Cоздано на {success_count} из {hosts_count} компьютеров.')
        self.finish_signal.emit(f'Внимание! Перед дальнейшей работой проверьте, что на вновь созданных '
                                f'учётных записях присутствует подключение к сети.')


# class RecreateStudent(QThread):
#     start_signal = pyqtSignal(str)
#     progress_signal = pyqtSignal(str)
#     finish_signal = pyqtSignal(str)
#
#     def __init__(self, parent=None):
#         QThread.__init__(self, parent)
#         self.hosts_list = None
#         # self.student_pass = None
#         self.hosts = Hosts()
#
#     def run(self) -> None:
#         """
#         Пересоздаёт учётки учеников на выбранных хостах
#         """
#         hosts_count = len(self.hosts_list)
#         success_count = 0
#         self.start_signal.emit(
#             f"Выбрано компьютеров: {hosts_count}<br>Пересоздание учётной записи ученика начинается<br>"
#         )
#         fail = []
#         for host in self.hosts.items_to_list():
#             if test_ssh(host) and host.hostname in self.hosts_list:
#                 remote_desktop_folder = run_command(
#                     f'ssh root@{host.hostname} sudo -u student xdg-user-dir DESKTOP').strip()
#                 works_folder = f'install -d -m 0755 -o {host.student_login} -g {host.student_login} \\"{remote_desktop_folder}/Сдать работы\\"'
#
#                 command = f'echo \'' \
#                           f'pkill -u {host.student_login}; ' \
#                           f'sleep 2; ' \
#                           f'userdel -rf {host.student_login}; ' \
#                           f'useradd {host.student_login} && ' \
#                           f'chpasswd <<<\'{host.student_login}:{host.student_pass}\' && ' \
#                           f'{works_folder} && reboot\' | at now'
#                 if check_student_on_host(host):
#                     run_command(f'ssh root@{host.hostname} \"{command}\"')
#                     self.progress_signal.emit(f'На {host.name} {host.student_login} удален и создан заново')
#                     logging.info(f'На {host.name} {host.student_login} удален и создан заново')
#                     success_count += 1
#                 else:
#                     run_command(f'ssh root@{host.hostname} \"{command}\"')
#                     self.progress_signal.emit(f'На {host.name} создан {host.student_login}')
#                     logging.info(f'На {host.name} создан {host.student_login}')
#                     success_count += 1
#             elif host.hostname in self.hosts_list:
#                 self.progress_signal.emit(f'{host.name} не в сети или не настроен ssh')
#                 logging.info(f'{host.name} не в сети или не настроен ssh')
#                 fail.append(f'{host.name} ({host.hostname})')
#         if success_count == 0:
#             self.finish_signal.emit(f"<br>Пересоздание учётных записей учеников не выполнено.")
#         elif success_count < hosts_count:
#             self.finish_signal.emit(
#                 f'<br>Пересоздание учётных записей учеников завершено.<br>'
#                 f'<font color="red">Выполнено на {success_count} из {hosts_count} устройств.</font>')
#             self.finish_signal.emit(f'<br>Не выполнено на устройствах: {", ".join(fail)}')
#         else:
#             self.finish_signal.emit(
#                 f'Пересоздание учётных записей учеников завершено.<br>'
#                 f'Пересоздано на {success_count} из {hosts_count} компьютеров.')


class DeleteStudent(QThread):
    start_signal = pyqtSignal(str)
    progress_signal = pyqtSignal(str)
    finish_signal = pyqtSignal(str)

    def __init__(self, parent=None):
        QThread.__init__(self, parent)
        self.hosts_list = None
        self.student_pass = ''

    def run(self) -> None:
        """
        Удаление учёток учеников на хостах
        """
        hosts_count = len(self.hosts_list)
        success_count = 0
        fail = []
        self.start_signal.emit(
            f"Выбрано компьютеров: {hosts_count}\nУдаление учётных записей учеников начинается...\n"
        )
        for host in self.hosts_list:
            if test_ssh(host):
                if check_student_on_host(host):
                    run_command(
                        f'ssh root@{host.hostname} py-ini-config del /etc/sddm.conf '
                        f'Autologin User --flush')
                    logging.info(f'Автологин студента на {host.hostname} выключен')
                    command = f'echo \'pkill -u {host.student_login} ; sleep 2 ; ' \
                              f'userdel -rf {host.student_login} ; reboot\' | at now'
                    run_command(f'ssh root@{host.hostname} \"{command}\"')
                    self.progress_signal.emit(f'{host.name}: {host.student_login} удален')
                    logging.info(f'{host.name} {host.student_login} удален')
                    success_count += 1
                else:
                    self.progress_signal.emit(f'{host.name}: отсутствует учётная запись ученика')
                    logging.info(f'{host.name} отсутствует учётная запись ученика')
                    fail.append(f'{host.name} ({host.hostname})')
            else:
                self.progress_signal.emit(f'{host.name}: не в сети или не настроен ssh')
                logging.info(f'{host.name} не в сети или не настроен ssh')
                fail.append(f'{host.name} ({host.hostname})')
        if success_count == 0:
            self.finish_signal.emit(f"\nУдаление учётных записей учеников не выполнено.")
        elif success_count < hosts_count:
            self.finish_signal.emit(
                f'<br>Удаление учётных записей учеников завершено. Компьютеры будут перезагружены.<br>'
                f'<font color="red">Выполнено на {success_count} из {hosts_count} устройств.</font>')
            self.finish_signal.emit(f'<br>Не выполнено на устройствах: {", ".join(fail)}')
        else:
            self.finish_signal.emit(
                f'Удаление учётных записей учеников завершено. Компьютеры будут перезагружены.<br>'
                f'Удалено на {success_count} из {hosts_count} компьютеров.')


class OpenSFTP(QThread):
    start_signal = pyqtSignal(str)
    progress_signal = pyqtSignal(str)
    finish_signal = pyqtSignal(str)

    def __init__(self, parent=None):
        QThread.__init__(self, parent)
        self.hosts_list = None
        self.hosts = Hosts()

    def run(self) -> None:
        """
        Открывает в Dolphin домашнюю папку выбранного ученика
        """
        hosts_count = 1
        success_count = 0
        self.start_signal.emit(
            f"Выбран компьютер: {self.hosts_list[0].name}<br>Директория открывается... "
            f"Внимание: это может занять некоторое время.<br>"
        )
        fail = []
        for host in self.hosts_list:
            if test_ssh(host):
                current_login_on_host = get_current_user_on_x(host)

                if current_login_on_host == host.student_login and not check_student_on_host(host):
                    self.progress_signal.emit(f'На {host.name} нет учётной записи {host.student_login}.')
                    fail.append(f'{host.name} ({host.hostname})')
                    continue
                self.progress_signal.emit(f'Текущий пользователь на {host.name}: {current_login_on_host}')
                current_home_folder = run_command('xdg-user-dir').strip()
                folder_for_mount = os.path.join(current_home_folder, 'teacher-control-remote')
                if os.path.isdir(folder_for_mount):
                    run_command(f'fusermount -u {folder_for_mount}')
                run_command(f'mkdir -p {folder_for_mount}')

                remote_home_folder = get_remote_desktop_folder(host, current_login_on_host) + '/..'
                run_command(f'sshfs root@{host.hostname}:{remote_home_folder} {folder_for_mount}')
                run_command(f'echo "DISPLAY=:0 xdg-open {folder_for_mount}" | at now')
                success_count += 1
                self.progress_signal.emit(f'{host.name}: открыт проводник')
                logging.info(f'{host.name} открыт sftp')
            else:
                self.progress_signal.emit(f'{host.name} не в сети или не настроен ssh')
                logging.info(f'{host.name} не в сети или не настроен ssh')
                fail.append(f'{host.name} ({host.hostname})')
        if success_count == 0:
            self.finish_signal.emit(f"<br>Открытие директории не выполнено.")
        elif success_count < hosts_count:
            self.finish_signal.emit(
                f'<br>Открытие директории завершено.<br>'
                f'<font color="red">Выполнено на {success_count} из {hosts_count} устройств.</font>')
            self.finish_signal.emit(f'<br>Не выполнено на устройствах: {", ".join(fail)}')
        else:
            self.finish_signal.emit(
                f'Открытие директории завершено.<br>'
                f'Выполнено на {success_count} из {hosts_count} компьютеров.')


class Autologin(QThread):
    start_signal = pyqtSignal(str)
    progress_signal = pyqtSignal(str)

    def __init__(self, turn_autologin_on: bool):
        super().__init__()
        self.host_list = None
        self.turn_autologin_on = turn_autologin_on

    def run(self) -> None:
        """
        Включает или выключает автологин учеников на выбранных хостах
        """
        hosts_count = len(self.host_list)
        success_count = 0
        self.start_signal.emit(
            f"Выбрано компьютеров: {hosts_count}<br>"
            f"{'Включение' if self.turn_autologin_on else 'Выключение'} автологина ученика начинается<br>"
        )
        fail = []
        for host in self.host_list:
            if test_ssh(host):
                if not check_student_on_host(host):
                    self.progress_signal.emit(f'На {host.name} нет учётной записи {host.student_login}.')
                    fail.append(f'{host.name} ({host.hostname})')
                    continue
                self.progress_signal.emit(f"{'Включение' if self.turn_autologin_on else 'Выключение'} "
                                          f"автологина {host.student_login} на {host.name}...")
                if self.turn_autologin_on:
                    run_command(
                        f'ssh root@{host.hostname} '
                        f'py-ini-config set /etc/sddm.conf Autologin User {host.student_login}')
                else:
                    run_command(
                        f'ssh root@{host.hostname} py-ini-config del /etc/sddm.conf '
                        f'Autologin User --flush')
                self.progress_signal.emit(f'Автологин {host.student_login} на {host.name} '
                                          f'{"Включён" if self.turn_autologin_on else "выключен"}')
                success_count += 1
            else:
                self.progress_signal.emit(f'{host.name} не в сети или не настроен ssh')
                logging.info(f'{host.hostname} не в сети или не настроен ssh')
                fail.append(f'{host.name} ({host.hostname})')
        if success_count == 0:
            self.progress_signal.emit(f"<br>{'Включение' if self.turn_autologin_on else 'Выключение'} "
                                      f"автологина не выполнено.")
        elif success_count < hosts_count:
            self.progress_signal.emit(
                f'<br>{"Включение" if self.turn_autologin_on else "Выключение"} автологина учеников завершено.<br>'
                f'<font color="red">Выполнено на {success_count} из {hosts_count} устройств.</font>')
            self.progress_signal.emit(f'<br>Не выполнено на устройствах: {", ".join(fail)}')
        else:
            self.progress_signal.emit(
                f'{"Включение" if self.turn_autologin_on else "Выключение"} автологина учеников завершено.<br>'
                f'Успешно на {success_count} из {hosts_count} компьютеров.')


class TasksCopyFiles(QThread):
    start_signal = pyqtSignal(str)
    progress_signal = pyqtSignal(str)
    finish_signal = pyqtSignal(str)

    def __init__(self):
        super().__init__()
        self.host_list = None
        self.files_list = []

    def run(self) -> None:
        """
        Копирует выбранные в файловом диалоге файлы с компьютера учителя в папку Задания на Рабочем столе учеников
        """
        hosts_count = len(self.host_list)
        success_count = 0
        fail = []
        self.start_signal.emit(
            f"Копирование заданий на {hosts_count} компьютеров...")
        self.files_list = [f'"{i}"' for i in self.files_list]
        for host in self.host_list:
            if not test_ssh(host):
                self.progress_signal.emit(f'На {host.name} не настроен ssh.')
                fail.append(f'{host.name} ({host.hostname})')
                continue
            current_login_on_host = get_current_user_on_x(host)
            if current_login_on_host == host.student_login and not check_student_on_host(host):
                self.progress_signal.emit(f'На {host.name} нет учётной записи {host.student_login}.')
                fail.append(f'{host.name} ({host.hostname})')
                continue
            self.progress_signal.emit(f'Копирование заданий на {host.name} пользователю {current_login_on_host}...')

            remote_desktop_folder = get_remote_desktop_folder(host, current_login_on_host)

            folder_to_copy = remote_desktop_folder.replace('\\', '')
            if run_command(f'ssh root@{host.hostname} "mkdir -p {remote_desktop_folder}/Задания && '
                           f'mkdir -p {remote_desktop_folder}/Сдать\ работы && '
                           f'chown {current_login_on_host} {remote_desktop_folder}/Сдать\ работы" && '
                           f'scp {" ".join(self.files_list)} root@{host.hostname}:'
                           f'"{folder_to_copy}/Задания" && echo 6').strip().endswith('6'):
                self.progress_signal.emit('Успешно')
                success_count += 1
            else:
                self.progress_signal.emit(f'Не удалось скопировать задания на {host.name}')
                fail.append(f'{host.name} ({host.hostname})')
        self.finish_signal.emit('Копирование завершено.')
        if success_count == len(self.host_list):
            self.finish_signal.emit(f'Успешно: {success_count} из {success_count} компьютеров.')
        else:
            self.finish_signal.emit(f'<font color="red">Успешно: {success_count} из {len(self.host_list)} '
                                    f'компьютеров.</font>')
            self.finish_signal.emit(f'Не удалось скопировать на компьютеры {", ".join(fail)}')
        if success_count > 0:
            self.finish_signal.emit('Ученики могут найти задания на Рабочем столе в папке Задания.')


class TasksCopyFolder(QThread):
    start_signal = pyqtSignal(str)
    progress_signal = pyqtSignal(str)
    finish_signal = pyqtSignal(str)

    def __init__(self):
        super().__init__()
        self.host_list = None
        self.folder_to_copy = ''

    def run(self) -> None:
        """
        Копирует выбранную в файловом диалоге папку с компьютера учителя в папку Задания на Рабочем столе учеников
        """
        hosts_count = len(self.host_list)
        success_count = 0
        fail = []
        self.start_signal.emit(
            f"Копирование заданий на {hosts_count} компьютеров...")

        for host in self.host_list:
            if not test_ssh(host):
                self.progress_signal.emit(f'На {host.name} не настроен ssh.')
                fail.append(f'{host.name} ({host.hostname})')
                continue
            current_login_on_host = get_current_user_on_x(host)
            if current_login_on_host == host.student_login and not check_student_on_host(host):
                self.progress_signal.emit(f'На {host.name} нет учётной записи {host.student_login}.')
                fail.append(f'{host.name} ({host.hostname})')
                continue
            self.progress_signal.emit(f'Копирование заданий на {host.name} пользователю {current_login_on_host}...')

            remote_desktop_folder = get_remote_desktop_folder(host, current_login_on_host)

            destination_folder = remote_desktop_folder.replace('\\', '')
            if run_command(f'ssh root@{host.hostname} "mkdir -p {remote_desktop_folder}/Задания && '
                           f'mkdir -p {remote_desktop_folder}/Сдать\ работы && '
                           f'chown {current_login_on_host} {remote_desktop_folder}/Сдать\ работы" && '
                           f'scp -r "{self.folder_to_copy}" root@{host.hostname}:'
                           f'"{destination_folder}/Задания" && echo 6').strip().endswith('6'):
                self.progress_signal.emit('Успешно')
                success_count += 1
            else:
                self.progress_signal.emit(f'Не удалось скопировать задания на {host.name}')
                fail.append(f'{host.name} ({host.hostname})')
        self.finish_signal.emit('Копирование завершено.')
        if success_count == len(self.host_list):
            self.finish_signal.emit(f'Успешно: {success_count} из {success_count} компьютеров.')
        else:
            self.finish_signal.emit(f'<font color="red">Успешно: {success_count} из {len(self.host_list)} '
                                    f'компьютеров.</font>')
            self.finish_signal.emit(f'Не удалось скопировать на компьютеры {", ".join(fail)}')
        if success_count > 0:
            self.finish_signal.emit('Ученики могут найти задания на Рабочем столе в папке Задания.')


class TasksClean(QThread):
    start_signal = pyqtSignal(str)
    progress_signal = pyqtSignal(str)
    finish_signal = pyqtSignal(str)

    def __init__(self):
        super().__init__()
        self.host_list = None

    def run(self) -> None:
        """
        Очищает папку Задания на Рабочем столе учеников на выбранных компьютерах
        """
        hosts_count = len(self.host_list)
        success_count = 0
        fail = []
        self.start_signal.emit(
            f"Очистка папки Задания на {hosts_count} компьютеров...")
        for host in self.host_list:
            if not test_ssh(host):
                self.progress_signal.emit(f'На {host.name} не настроен ssh.')
                fail.append(f'{host.name} ({host.hostname})')
                continue
            current_login_on_host = get_current_user_on_x(host)
            if current_login_on_host == host.student_login and not check_student_on_host(host):
                self.progress_signal.emit(f'На {host.name} нет учётной записи {host.student_login}.')
                fail.append(f'{host.name} ({host.hostname})')
                continue
            self.progress_signal.emit(f'Очистка заданий на {host.name} у пользователя {current_login_on_host}...')

            remote_desktop_folder = get_remote_desktop_folder(host, current_login_on_host)

            if run_command(f'ssh root@{host.hostname} "rm -rf {remote_desktop_folder}/Задания/*" && '
                           f'echo 6').strip().endswith('6'):
                self.progress_signal.emit('Успешно')
                success_count += 1
            else:
                self.progress_signal.emit(f'Не удалось очистить задания на {host.name}')
                fail.append(f'{host.name} ({host.hostname})')
        self.finish_signal.emit('Удаление завершено.')
        if success_count == len(self.host_list):
            self.finish_signal.emit(f'Успешно: {success_count} из {success_count} компьютеров.')
        else:
            self.finish_signal.emit(f'<font color="red">Успешно: {success_count} из {len(self.host_list)} '
                                    f'компьютеров.</font>')
            self.finish_signal.emit(f'Не удалось очистить на компьютерах {", ".join(fail)}')


class ShowHostsNames(QThread):
    start_signal = pyqtSignal(str)
    progress_signal = pyqtSignal(str)
    finish_signal = pyqtSignal(str)

    def __init__(self):
        super().__init__()
        self.host_list = None
        self.show_alert = False

    def run(self) -> None:
        """
        Показывает имена хостов на выбранных компьютерах
        """
        success_count = 0
        fail = []
        for host in self.host_list:
            if not test_ssh(host):
                self.progress_signal.emit(f'На {host.name} не настроен ssh.')
                fail.append(f'{host.name} ({host.hostname})')
                continue
            current_login_on_host = get_current_user_on_x(host)
            if current_login_on_host == host.student_login and not check_student_on_host(host):
                self.progress_signal.emit(f'На {host.name} нет учётной записи {host.student_login}.')
                fail.append(f'{host.name} ({host.hostname})')
                continue
            self.progress_signal.emit(f'Отображаем имя {host.name} у пользователя {current_login_on_host}...')
            number_size = 20
            string_to_show = host.name
            if self.show_alert:
                number_size = 10
                string_to_show = 'Внимание! Идёт обновление. Не выключайте компьютер!'
            open_bigs = '<big>' * number_size
            close_bigs = '</big>' * number_size
            command_to_run = f'ssh root@{host.hostname} ' \
                             f'"su - {current_login_on_host} bash -c \'at now <<< \\\"DISPLAY=:0 zenity --info ' \
                             f'--text=\'\\\"\'{open_bigs}{string_to_show}{close_bigs}\'\\\"\' ' \
                             f'--title=\'\\\"\'Host name\'\\\"\' --timeout=3\\\" \' "'
            if self.show_alert:
                command_to_run = command_to_run.replace('--timeout=3', '')
            run_command(command_to_run)
            success_count += 1

        self.finish_signal.emit('Команда отправлена на устройства.')
        if success_count == len(self.host_list):
            self.finish_signal.emit(f'Успешно: {success_count} из {success_count} компьютеров.')
        else:
            self.finish_signal.emit(f'<font color="red">Успешно: {success_count} из {len(self.host_list)} '
                                    f'компьютеров.</font>')
            self.finish_signal.emit(f'Не удалось отобразить на компьютерах {", ".join(fail)}')


class CreateWorksFolderThread(QThread):
    start_signal = pyqtSignal(str)
    progress_signal = pyqtSignal(str)
    finish_signal = pyqtSignal(str)

    def __init__(self):
        super().__init__()
        self.host_list = None

    def run(self) -> None:
        """
        Создаёт папку Сдать работы на Рабочем столе учеников на выбранных компьютерах
        """
        hosts_count = len(self.host_list)
        success_count = 0
        fail = []
        self.start_signal.emit(
            f"Создание папки Сдать работы на {hosts_count} компьютеров...")
        for host in self.host_list:
            if not test_ssh(host):
                self.progress_signal.emit(f'На {host.name} не настроен ssh.')
                fail.append(f'{host.name} ({host.hostname})')
                continue
            current_login_on_host = get_current_user_on_x(host)
            if current_login_on_host == host.student_login and not check_student_on_host(host):
                self.progress_signal.emit(f'На {host.name} нет учётной записи {host.student_login}.')
                fail.append(f'{host.name} ({host.hostname})')
                continue
            self.progress_signal.emit(f'Создание папки Сдать работы на {host.name} '
                                      f'для пользователя {current_login_on_host}...')

            remote_desktop_folder = get_remote_desktop_folder(host, current_login_on_host)

            if run_command(f'ssh root@{host.hostname} '
                           f'"mkdir -p {remote_desktop_folder}/Сдать\ работы && '
                           f'chown {current_login_on_host} {remote_desktop_folder}/Сдать\ работы" && '
                           f'echo 6').strip().endswith('6'):
                self.progress_signal.emit('Успешно')
                success_count += 1
            else:
                self.progress_signal.emit(f'Не удалось создать папку Сдать работы на {host.name}')
                fail.append(f'{host.name} ({host.hostname})')
        self.finish_signal.emit('Создание папок завершено.')
        if success_count == len(self.host_list):
            self.finish_signal.emit(f'Успешно: {success_count} из {success_count} компьютеров.')
        else:
            self.finish_signal.emit(f'<font color="red">Успешно: {success_count} из {len(self.host_list)} '
                                    f'компьютеров.</font>')
            self.finish_signal.emit(f'Не удалось создать на компьютерах {", ".join(fail)}')


class OpenBrowserWithPage(QThread):
    start_signal = pyqtSignal(str)
    progress_signal = pyqtSignal(str)
    finish_signal = pyqtSignal(str)

    def __init__(self):
        super().__init__()
        self.host_list = None
        self.page = ''
        self.open_incognito = False
        self.open_fullscreen = False

    def run(self) -> None:
        """
        Открывает бразуер с указанной страницей на выбранных хостах
        """
        hosts_count = len(self.host_list)
        success_count = 0
        fail = []
        self.start_signal.emit(
            f"Открытие браузера на {hosts_count} компьютеров...")
        for host in self.host_list:
            if not test_ssh(host):
                self.progress_signal.emit(f'На {host.name} не настроен ssh.')
                fail.append(f'{host.name} ({host.hostname})')
                continue
            current_login_on_host = get_current_user_on_x(host)
            if current_login_on_host == host.student_login and not check_student_on_host(host):
                self.progress_signal.emit(f'На {host.name} нет учётной записи {host.student_login}.')
                fail.append(f'{host.name} ({host.hostname})')
                continue
            self.progress_signal.emit(f'Открытие страницы {self.page} на {host.name} '
                                      f'у пользователя {current_login_on_host}...')
            if not self.page.startswith('https://'):
                self.page = 'https://' + self.page.split('://')[-1]
            flags = ''
            if self.open_incognito:
                flags += '--incognito '
            if self.open_fullscreen:
                flags += '--start-fullscreen '
            command = f'su - {current_login_on_host} bash -c \'at now <<< \\\"DISPLAY=:0 ' \
                      f'chromium-browser {self.page} {flags}\\\" \' '
            if run_command(f'ssh root@{host.hostname} \"{command}\" '
                           f'&& echo 6').strip().endswith('6'):
                self.progress_signal.emit('Успешно')
                success_count += 1
            else:
                self.progress_signal.emit(f'Не удалось запустить браузер на {host.name}')
                fail.append(f'{host.name} ({host.hostname})')
        self.finish_signal.emit('Открытие браузера завершено.')
        if success_count == len(self.host_list):
            self.finish_signal.emit(f'Успешно: {success_count} из {success_count} компьютеров.')
        else:
            self.finish_signal.emit(f'<font color="red">Успешно: {success_count} из {len(self.host_list)} '
                                    f'компьютеров.</font>')
            self.finish_signal.emit(f'Не удалось открыть на компьютерах {", ".join(fail)}')


class ArchiveStudent(QThread):
    start_signal = pyqtSignal(str)
    progress_signal = pyqtSignal(str)
    finish_signal = pyqtSignal(str)

    def __init__(self):
        super().__init__()
        self.host_list = None

    def run(self) -> None:
        """
        Создаёт архив из домашней папки студента на выбранных компьютерах
        """
        hosts_count = len(self.host_list)
        success_count = 0
        fail = []
        self.start_signal.emit(
            f"Создание архива домашней папки ученика на {hosts_count} компьютеров...")
        for host in self.host_list:
            if not test_ssh(host):
                self.progress_signal.emit(f'На {host.name} не настроен ssh.')
                fail.append(f'{host.name} ({host.hostname})')
                continue
            if not check_student_on_host(host):
                self.progress_signal.emit(f'На {host.name} нет учётной записи {host.student_login}.')
                fail.append(f'{host.name} ({host.hostname})')
                continue

            self.progress_signal.emit(f'Создание архива /home/{host.student_login} на {host.name}...')
            current_home_folder = run_command(f'ssh root@{host.hostname} '
                                              f'"sudo -u {host.student_login} '
                                              f'xdg-user-dir"').strip().replace(' ', '\\ ')
            # чтобы в архив не добавлялось всё дерево папок
            home_folder_path, clean_home_folder_name = current_home_folder.rsplit('/', 1)
            if run_command(f'ssh root@{host.hostname} '
                           f'\'echo "tar -C {home_folder_path} -cvf /home/{host.student_login}.tar.gz '
                           f'{clean_home_folder_name}" | at now\' && '
                           f'echo 830').strip().endswith('830'):
                self.progress_signal.emit('Успешно')
                success_count += 1
            else:
                self.progress_signal.emit(f'Не удалось создать архив /home/{host.student_login} на {host.name}')
                fail.append(f'{host.name} ({host.hostname})')
        self.finish_signal.emit('Создание архивов завершено.')
        if success_count == len(self.host_list):
            self.finish_signal.emit(f'Успешно: {success_count} из {success_count} компьютеров.')
        else:
            self.finish_signal.emit(f'<font color="red">Успешно: {success_count} из {len(self.host_list)} '
                                    f'компьютеров.</font>')
            self.finish_signal.emit(f'Не удалось создать архив на компьютерах {", ".join(fail)}')


class RestoreStudentFromArchive(QThread):
    start_signal = pyqtSignal(str)
    progress_signal = pyqtSignal(str)
    finish_signal = pyqtSignal(str)

    def __init__(self):
        super().__init__()
        self.host_list = None

    def run(self) -> None:
        """
        Восстанавливает домашнюю папку ученика из архива на выбранных компьютерах
        """
        hosts_count = len(self.host_list)
        success_count = 0
        fail = []
        self.start_signal.emit(
            f"Восстановление домашней папки ученика из архива на {hosts_count} компьютеров...")
        for host in self.host_list:
            if not test_ssh(host):
                self.progress_signal.emit(f'На {host.name} не настроен ssh.')
                fail.append(f'{host.name} ({host.hostname})')
                continue
            if not check_student_on_host(host):
                self.progress_signal.emit(f'На {host.name} нет учётной записи {host.student_login}.')
                fail.append(f'{host.name} ({host.hostname})')
                continue
            self.progress_signal.emit(f'Восстановление /home/{host.student_login} на {host.name}...')
            current_home_folder = run_command(f'ssh root@{host.hostname} '
                                              f'"sudo -u {host.student_login} '
                                              f'xdg-user-dir"').strip().replace(' ', '\\ ')
            if 'cannot open' in run_command(f'ssh root@{host.hostname} '
                                            f'"file /home/{host.student_login}.tar.gz"'):
                self.progress_signal.emit(f'На {host.name} нет архива для {host.student_login}.')
                logging.info(f'На {host.name} нет архива для {host.student_login}.')
                fail.append(f'{host.name} ({host.hostname})')
                continue
            if run_command(f'ssh root@{host.hostname} '
                           f'\'echo "pkill -u {host.student_login}; sleep 2; rm -rf {current_home_folder} ; '
                           f'tar -C /home -xf /home/{host.student_login}.tar.gz; '
                           f'chown {host.student_login}:{host.student_login} -R {current_home_folder}; '
                           f'reboot" | at now\' && '
                           f'echo 830').strip().endswith('830'):
                self.progress_signal.emit('Успешно')
                success_count += 1
            else:
                self.progress_signal.emit(f'Не удалось восстановить /home/{host.student_login} из архива '
                                          f'на {host.name}')
                fail.append(f'{host.name} ({host.hostname})')
        self.finish_signal.emit('Восстановление домашних папок учеников завершено.<br>'
                                'Дождитесь перезагрузки устройств.')
        if success_count == len(self.host_list):
            self.finish_signal.emit(f'Успешно: {success_count} из {success_count} компьютеров.')
        else:
            self.finish_signal.emit(f'<font color="red">Успешно: {success_count} из {len(self.host_list)} '
                                    f'компьютеров.</font>')
            self.finish_signal.emit(f'Не удалось восстановить на компьютерах {", ".join(fail)}')


class VeyonRemoveKeys(QThread):
    start_signal = pyqtSignal(str)
    progress_signal = pyqtSignal(str)
    finish_signal = pyqtSignal(str)

    def __init__(self):
        super().__init__()
        self.host_list = None

    def run(self) -> None:
        """
        Удаляет ключи veyon на выбранных компьютерах
        """
        success_count = 0
        fail = []
        for host in self.host_list:
            if not test_ssh(host):
                self.progress_signal.emit(f'На {host.name} не настроен ssh.')
                fail.append(f'{host.name} ({host.hostname})')
                continue
            is_veyon_installed = run_command(f'ssh root@{host.hostname} rpm -q veyon-core').strip().startswith(
                'veyon-core')
            if not is_veyon_installed:
                self.progress_signal.emit(f'На {host.name} не установлен veyon-core. Ключи не могут быть найдены.')
                fail.append(f'{host.name} ({host.hostname})')
                continue
            found_keys = run_command(f'ssh root@{host.hostname} veyon-cli authkeys list').strip().split('\n')
            found_keys = [i for i in found_keys if 'Logger' not in i and 'QStandardPaths' not in i]
            while '' in found_keys:
                found_keys.remove('')
            if not found_keys:
                self.progress_signal.emit(f'На {host.name} нет ключей veyon.')
                success_count += 1
                continue
            self.progress_signal.emit(f'Удаление ключей {", ".join(found_keys)} на {host.name}...')
            commands = [f'veyon-cli authkeys delete {key}' for key in found_keys]
            command = '; '.join(commands)
            res = run_command(f'ssh root@{host.hostname} {command}')
            self.progress_signal.emit(f'Результат удаления на {host.name}: {res}')
            logging.info(f'Результат удаления ключей veyon на {host.name}: {res}')
            success_count += 1

        self.finish_signal.emit('Удаление ключей veyon на хостах завершено.')
        if success_count == len(self.host_list):
            self.finish_signal.emit(f'Успешно: {success_count} из {success_count} компьютеров.')
        else:
            self.finish_signal.emit(f'<font color="red">Успешно: {success_count} из {len(self.host_list)} '
                                    f'компьютеров.</font>')
            self.finish_signal.emit(f'Не удалось удалить на компьютерах {", ".join(fail)}')


class CopyStudentArchiveFromServer(QThread):
    start_signal = pyqtSignal(str)
    progress_signal = pyqtSignal(str)
    finish_signal = pyqtSignal(str)

    def __init__(self):
        super().__init__()
        self.host_list = None
        self.login = ''

    def run(self) -> None:
        """
        Создаёт и копирует архив /home ученика на выбранные хосты для дальнейшего восстановления
        """
        hosts_count = len(self.host_list)
        success_count = 0
        fail = []
        self.start_signal.emit(
            f"Копирование архива домашней папки ученика с сервера на {hosts_count} компьютеров...")
        last_login = self.login
        temp_folder = run_command('mktemp -d').strip()
        print(temp_folder)
        run_command_by_root(f'cp -r /home/{self.login}/ {temp_folder}/{self.login} && '
                            f'chown -R {user}:{user} {temp_folder}/{self.login}')
        for host in self.host_list:
            if not test_ssh(host):
                self.progress_signal.emit(f'На {host.name} не настроен ssh.')
                fail.append(f'{host.name} ({host.hostname})')
                continue
            if not check_student_on_host(host):
                self.progress_signal.emit(f'На {host.name} нет учётной записи {host.student_login}.')
                fail.append(f'{host.name} ({host.hostname})')
                continue
            self.progress_signal.emit(f'Копирование архива /home/{host.student_login}.tar.gz на {host.name}...')
            if not os.path.isfile(f'{temp_folder}/{host.student_login}.tar.gz'):
                if run_command(f'mv /{temp_folder}/{last_login} {temp_folder}/{host.student_login} ; '
                               f'tar -C {temp_folder} -cvf {temp_folder}/{host.student_login}.tar.gz '
                               f'{host.student_login} && '
                               f'echo 830').strip().endswith('830'):
                    self.progress_signal.emit(f'На сервере создан архив {host.student_login} для {host.name}...')
                    last_login = host.student_login
                else:
                    self.progress_signal.emit(f'Не удалось создать архив {host.student_login} на сервере.')
                    fail.append(f'{host.name} ({host.hostname})')
                    continue
            result = run_command(
                f'scp {temp_folder}/{host.student_login}.tar.gz root@{host.hostname}:/home/ && echo 830')
            if not result.strip().endswith('830'):
                self.progress_signal.emit(f'Копирование на {host.name} завершилось с ошибкой.')
                logging.info(f'Копирование архива на {host.name} не удалось.')
                fail.append(f'{host.name} ({host.hostname})')
                continue

            success_count += 1

        run_command(f'rm -rf {temp_folder}')
        self.finish_signal.emit('Копирование архива домашней папки ученика завершено.')
        if last_login != self.login:
            run_command(f'mv /home/{last_login} /home/{self.login}')
        if success_count == len(self.host_list):
            self.finish_signal.emit(f'Успешно: {success_count} из {success_count} компьютеров.')
        else:
            self.finish_signal.emit(f'<font color="red">Успешно: {success_count} из {len(self.host_list)} '
                                    f'компьютеров.</font>')
            self.finish_signal.emit(f'Не удалось скопировать на компьютеры {", ".join(fail)}')


class OpenWorksFolderThread(QThread):
    progress_signal = pyqtSignal(str)
    finish_signal = pyqtSignal(str)

    def __init__(self):
        super().__init__()

    def run(self) -> None:
        """
        Открывает папку Работы на сервере
        """
        current_home_folder = run_command('xdg-user-dir DESKTOP').strip()
        if not os.path.isdir(f'{current_home_folder}/Работы'):
            self.finish_signal.emit('Папка Работы не существует.')
        else:
            self.progress_signal.emit('Папка Работы открыта.')
            run_command(F'dolphin "{current_home_folder}/Работы"')


class RestrictDesktopChanges(QThread):
    start_signal = pyqtSignal(str)
    progress_signal = pyqtSignal(str)
    finish_signal = pyqtSignal(str)

    def __init__(self, restrict=False):
        super().__init__()
        self.host_list = None
        self.restrict = restrict

    def run(self) -> None:
        """
        Включение или выключение ограничений на изменение профиля
        """
        hosts_count = len(self.host_list)
        success_count = 0
        fail = []
        self.start_signal.emit(
            f"В{'' if self.restrict else 'ы'}ключение ограничений Рабочего стола на {hosts_count} компьютеров...")
        for host in self.host_list:
            if not test_ssh(host):
                self.progress_signal.emit(f'На {host.name} не настроен ssh.')
                fail.append(f'{host.name} ({host.hostname})')
                continue
            current_login_on_host = get_current_user_on_x(host)
            if current_login_on_host == host.student_login and not check_student_on_host(host):
                self.progress_signal.emit(f'На {host.name} нет учётной записи {host.student_login}.')
                fail.append(f'{host.name} ({host.hostname})')
                continue
            self.progress_signal.emit(f"В{'' if self.restrict else 'ы'}ключение ограничений на {host.name} "
                                      f"для пользователя {current_login_on_host}...")

            remote_home_folder = get_remote_desktop_folder(host, current_login_on_host) + '/..'

            if self.restrict and run_command(f'ssh root@{host.hostname} '
                                             f'"chown root:{current_login_on_host} '
                                             f'{remote_home_folder}/.config/plasma-org.kde.plasma.desktop-appletsrc && '
                                             f'chmod 040 {remote_home_folder}/.config/plasma-org.kde.plasma.desktop-appletsrc" && '
                                             f'echo 6').strip().endswith('6'):
                self.progress_signal.emit('Успешно')
                success_count += 1
            elif not self.restrict and run_command(f'ssh root@{host.hostname} '
                                                   f'"chown {current_login_on_host}:{current_login_on_host} '
                                                   f'{remote_home_folder}/.config/plasma-org.kde.plasma.desktop-appletsrc && '
                                                   f'chmod 600 {remote_home_folder}/.config/plasma-org.kde.plasma.desktop-appletsrc" && '
                                                   f'echo 6').strip().endswith('6'):
                self.progress_signal.emit('Успешно')
                success_count += 1
            else:
                self.progress_signal.emit(f"Не удалось в{'' if self.restrict else 'ы'}ключить ограничения "
                                          f"на {host.name}")
                fail.append(f'{host.name} ({host.hostname})')
        self.finish_signal.emit(f"В{'' if self.restrict else 'ы'}ключение ограничений завершено.")
        if success_count == len(self.host_list):
            self.finish_signal.emit(f'Успешно: {success_count} из {success_count} компьютеров.')
        else:
            self.finish_signal.emit(f'<font color="red">Успешно: {success_count} из {len(self.host_list)} '
                                    f'компьютеров.</font>')
            self.finish_signal.emit(f"Не удалось в{'' if self.restrict else 'ы'}ключить "
                                    f"на компьютерах {', '.join(fail)}")


class RestrictNetwork(QThread):
    start_signal = pyqtSignal(str)
    progress_signal = pyqtSignal(str)
    finish_signal = pyqtSignal(str)

    def __init__(self, restrict=False):
        super().__init__()
        self.host_list = None
        self.restrict = restrict

    def run(self) -> None:
        """
        Включение или выключение интернета на хостах
        """
        hosts_count = len(self.host_list)
        success_count = 0
        fail = []
        self.start_signal.emit(
            f"В{'ы' if self.restrict else ''}ключение интернета на {hosts_count} компьютеров...")
        if self.restrict:
            run_command('rm -rf ~/.ssh/config')
        for host in self.host_list:
            if not test_ssh(host):
                self.progress_signal.emit(f'На {host.name} не настроен ssh.')
                fail.append(f'{host.name} ({host.hostname})')
                continue
            current_login_on_host = get_current_user_on_x(host)
            if current_login_on_host == host.student_login and not check_student_on_host(host):
                self.progress_signal.emit(f'На {host.name} нет учётной записи {host.student_login}.')
                fail.append(f'{host.name} ({host.hostname})')
                continue
            self.progress_signal.emit(f"В{'ы' if self.restrict else ''}ключение интернета на {host.name} "
                                      f"для пользователя {current_login_on_host}...")
            # host_ip = run_command(f'ssh root@{host.hostname} "hostname -I"').split('\n')[0].strip()
            # TODO проверить два IP, и возможность заменить на localname или IP из таблицы
            host_config = f"""
                Host {host.hostname}
                HostName {host.hostname}
                User {host.admin_login}
                Port 22
                
            """
            print(host_config, file=open(f'/home/{user}/.ssh/config', 'a'))
            current_host_success = False
            remote_sysconfig_folder = run_command(f'ssh root@{host.hostname} ls /etc/sysconfig').split('\n')
            if 'nftables.conf.old' not in [i.strip() for i in remote_sysconfig_folder]:
                run_command(f'ssh root@{host.hostname} '
                            f'"cp /etc/sysconfig/nftables.conf /etc/sysconfig/nftables.conf.old"')
            if self.restrict:
                restriction_rules = """flush ruleset
                
                table inet filter {
                    chain input {
                        type filter hook input priority 0; policy accept;
                        tcp dport {80, 443, 8080, 8443, 23, 25, 69, 993, 995, 465, 587, 39, 4664} drop
                        udp dport {80, 443, 8080, 8443, 23, 25, 69, 993, 995, 465, 587, 39} drop
                    }
                
                    chain output {
                        type filter hook output priority 0; policy accept;
                        tcp dport {80, 443, 8080, 8443, 23, 25, 69, 993, 995, 465, 587, 39, 4664} drop
                        udp dport {80, 443, 8080, 8443, 23, 25, 69, 993, 995, 465, 587, 39} drop
                    }
                }
                """

                if run_command(f'ssh root@{host.hostname} \'echo "{restriction_rules}" > /etc/sysconfig/nftables.conf '
                               f'&& echo "systemctl restart nftables.service" | at now\' && '
                               f'echo 6').strip().endswith('6'):
                    if run_command(
                            f'ssh root@{host.hostname} \'systemctl enable nftables.service\' && '
                            f'echo 6').strip().endswith('6'):
                        self.progress_signal.emit('Успешно')
                        current_host_success = True
            else:
                if run_command(f'ssh root@{host.hostname} '
                               f'"cp -f /etc/sysconfig/nftables.conf.old /etc/sysconfig/nftables.conf && '
                               f' systemctl restart nftables.service " && '
                               f'echo 6').strip().endswith('6'):
                    self.progress_signal.emit('Успешно')
                    current_host_success = True
            if current_host_success:
                success_count += 1
            else:
                self.progress_signal.emit(f"Не удалось в{'ы' if self.restrict else ''}ключить интернет "
                                          f"на {host.name}")
                fail.append(f'{host.name} ({host.hostname})')
        self.finish_signal.emit(f"В{'ы' if self.restrict else ''}ключение интернета завершено.")
        if success_count == len(self.host_list):
            self.finish_signal.emit(f'Успешно: {success_count} из {success_count} компьютеров.')
        else:
            self.finish_signal.emit(f'<font color="red">Успешно: {success_count} из {len(self.host_list)} '
                                    f'компьютеров.</font>')
            self.finish_signal.emit(f"Не удалось в{'ы' if self.restrict else ''}ключить "
                                    f"на компьютерах {', '.join(fail)}")


class FileAssociationsThread(QThread):
    start_signal = pyqtSignal(str)
    progress_signal = pyqtSignal(str)
    finish_signal = pyqtSignal(str)

    def __init__(self):
        super().__init__()
        self.host_list = None
        self.associations_to_set = dict()

    def run(self) -> None:
        """
        Устанавливает выбранные файловые ассоциации
        """
        if len(self.associations_to_set) == 0:
            self.start_signal.emit(
                f"Файловые ассоциации не будут изменены.")
            return
        hosts_count = len(self.host_list)
        success_count = 0
        fail = []
        self.start_signal.emit(
            f"Установка файловых ассоциаций на {hosts_count} компьютеров...")
        for host in self.host_list:
            if not test_ssh(host):
                self.progress_signal.emit(f'На {host.name} не настроен ssh.')
                fail.append(f'{host.name} ({host.hostname})')
                continue
            current_login_on_host = get_current_user_on_x(host)
            if current_login_on_host == host.student_login and not check_student_on_host(host):
                self.progress_signal.emit(f'На {host.name} нет учётной записи {host.student_login}.')
                fail.append(f'{host.name} ({host.hostname})')
                continue

            self.progress_signal.emit(f'Установка ассоциаций для {current_login_on_host} на {host.name}...')
            current_home_folder = run_command(f'ssh root@{host.hostname} '
                                              f'"sudo -u {current_login_on_host} '
                                              f'xdg-user-dir"').strip().replace(' ', '\\ ')
            if 'mimeapps.list' not in run_command(f'ssh root@{host.hostname} "ls {current_home_folder}/.config"'):
                run_command(f'ssh root@{host.hostname} "touch {current_home_folder}/.config/mimeapps.list"')
            command = ''
            for key in self.associations_to_set:
                command += (f'py-ini-config set {current_home_folder}/.config/mimeapps.list "Default Applications" '
                            f'{key} {self.associations_to_set[key]} && ')
            command = command.rstrip('&& ')
            if run_command(f'ssh root@{host.hostname} \'{command}\' && echo 830').strip().endswith('830'):
                self.progress_signal.emit('Успешно')
                success_count += 1
            else:
                self.progress_signal.emit(f'Не удалось установить файловые ассоциации для '
                                          f'{current_login_on_host} на {host.name}')
                fail.append(f'{host.name} ({host.hostname})')
        self.finish_signal.emit('Установка ассоциаций завершена.')
        if success_count == len(self.host_list):
            self.finish_signal.emit(f'Успешно: {success_count} из {success_count} компьютеров.')
        else:
            self.finish_signal.emit(f'<font color="red">Успешно: {success_count} из {len(self.host_list)} '
                                    f'компьютеров.</font>')
            self.finish_signal.emit(f'Не удалось установить на компьютерах {", ".join(fail)}')


def reset_log_to_last_week(log_file: str) -> None:
    """
    Удаляет все записи лога, кроме последней недели
    @rtype: None
    """
    with open(log_file) as inp:
        lines = inp.readlines()

    week_ago = datetime.today() - timedelta(7)
    first_line = -1
    for i in range(len(lines)):
        lines[i] = lines[i].strip()

    for i in range(len(lines)):
        try:
            date = datetime.strptime(lines[i].split()[0], "%Y-%m-%d")
        except Exception:
            continue

        if date.date() >= week_ago.date():
            first_line = i
            break
    if first_line == -1:
        first_line = len(lines)

    lines = lines[first_line:]
    while '' in lines:
        lines.remove('')
    while '\n' in lines:
        lines.remove('\n')
    with open(log_file, 'w') as out:
        print(*lines, file=out, sep='\n')


class SetDesktopBackground(QThread):
    start_signal = pyqtSignal(str)
    progress_signal = pyqtSignal(str)
    finish_signal = pyqtSignal(str)

    def __init__(self, image=None):
        super().__init__()
        self.host_list = None
        self.picture = image

    def run(self) -> None:
        """
        Меняет у учеников на хостах картинку рабочего стола
        """
        hosts_count = len(self.host_list)
        success_count = 0
        fail = []
        self.start_signal.emit(
            f"Смена обоев на {hosts_count} компьютеров...")
        for host in self.host_list:
            if not test_ssh(host):
                self.progress_signal.emit(f'На {host.name} не настроен ssh.')
                fail.append(f'{host.name} ({host.hostname})')
                continue
            current_login_on_host = get_current_user_on_x(host)
            if current_login_on_host == host.student_login and not check_student_on_host(host):
                self.progress_signal.emit(f'На {host.name} нет учётной записи {host.student_login}.')
                fail.append(f'{host.name} ({host.hostname})')
                continue
            self.progress_signal.emit(f'Смена обоев на {host.name} пользователю {current_login_on_host}...')

            if current_login_on_host != 'guest':
                remote_home_folder = run_command(f'ssh root@{host.hostname} '
                                                 f'"sudo -u {current_login_on_host} '
                                                 f'xdg-user-dir"').strip().replace(' ', '\\ ')
            else:
                remote_home_folder = '/home/guest'

            picture_name_without_path = self.picture.split('/')[-1].strip().replace(' ', '\\ ')
            if run_command(f'scp "{self.picture}" root@{host.hostname}:'
                           f'"{remote_home_folder}/.config" && '
                           f'ssh root@{host.hostname} "chown {current_login_on_host}:{current_login_on_host} '
                           f'{remote_home_folder}/.config/{picture_name_without_path} && '
                           f'machinectl shell {current_login_on_host}@ /usr/bin/plasma-apply-wallpaperimage '
                           f'{remote_home_folder}/.config/{picture_name_without_path} " '
                           f'&& echo 6').strip().endswith('6'):
                self.progress_signal.emit('Успешно')
                success_count += 1
            else:
                self.progress_signal.emit(f'Не удалось поменять обои на {host.name}')
                fail.append(f'{host.name} ({host.hostname})')
        self.finish_signal.emit('Смена обоев завершена.')
        if success_count == len(self.host_list):
            self.finish_signal.emit(f'Успешно: {success_count} из {success_count} компьютеров.')
        else:
            self.finish_signal.emit(f'<font color="red">Успешно: {success_count} из {len(self.host_list)} '
                                    f'компьютеров.</font>')
            self.finish_signal.emit(f'Не удалось поменять на компьютерах {", ".join(fail)}')


class EnableRemoteLogin(QThread):
    start_signal = pyqtSignal(str)
    progress_signal = pyqtSignal(str)
    finish_signal = pyqtSignal(str)

    def __init__(self):
        super().__init__()
        self.host_list = None
        self.hosts = None

    def run(self) -> None:
        """
        Включает возможность удалённого входа на выбранных хостах
        """
        hosts_count = len(self.host_list)
        success_count = 0
        fail = []
        self.start_signal.emit(
            f"Включение возможности удалённого входа на {hosts_count} компьютеров...")
        for host in self.host_list:
            if not test_ssh(host):
                self.progress_signal.emit(f'На {host.name} не настроен ssh.')
                fail.append(f'{host.name} ({host.hostname})')
                continue
            self.progress_signal.emit(f'Включение возможности удалённого входа в систему на {host.name}...')
            # disable нужен для корректного кода возврата всей команды
            if run_command(f'ssh root@{host.hostname} "dnf in mos-auth-dbus-conf -y && mos-auth-config disable && '
                           f'mos-auth-config enable" && echo 830').strip().endswith('830'):
                self.progress_signal.emit(f'На {host.name} включена возможность удалённого входа в систему.')
                success_count += 1
                run_command(f'ssh root@{host.hostname} reboot')
                self.progress_signal.emit(f'Перезагружаем {host.name}...')
                self.hosts.set_true_remote_login_setup(host.name)
            else:
                if not run_command(f'ssh root@{host.hostname} "rpm -q mos-auth-dbus-conf"').strip().startswith('mos-'):
                    self.progress_signal.emit(f'Не удалось установить пакет mos-auth-dbus-conf на {host.name}')
                else:
                    self.progress_signal.emit(f'Не удалось включить авторизацию через mos.ru на {host.name}')
                fail.append(f'{host.name} ({host.hostname})')
                continue

        self.finish_signal.emit('Включение возможности удалённого входа в систему завершено.')
        if success_count == len(self.host_list):
            self.finish_signal.emit(f'Успешно: {success_count} из {success_count} компьютеров.')
        else:
            self.finish_signal.emit(f'<font color="red">Успешно: {success_count} из {len(self.host_list)} '
                                    f'компьютеров.</font>')
            self.finish_signal.emit(f'Не удалось включить на компьютерах {", ".join(fail)}')


class LoginStudents(QThread):
    start_signal = pyqtSignal(str)
    progress_signal = pyqtSignal(str)
    finish_signal = pyqtSignal(str)

    def __init__(self):
        super().__init__()
        self.host_list = None

    def run(self) -> None:
        """
        Выполняет удалённый вход в систему учеников на выбранных хостах
        """
        hosts_count = len(self.host_list)
        self.start_signal.emit(
            f"Выполняется удалённый вход в систему учеников на {hosts_count} компьютеров...")
        for host in self.host_list:
            if not test_ssh(host):
                self.progress_signal.emit(f'На {host.name} не настроен ssh.')
                continue
            if not host.student_login:
                self.progress_signal.emit(f'Не указано имя пользователя для входа на {host.name}')
                continue
            self.progress_signal.emit(f'Удалённый вход {host.student_login} на {host.name}...')
            run_command(f'ssh root@{host.hostname} '
                        f'"echo \\\"qdbus --system org.mos.auth / login {host.student_login} \'{host.student_pass}\' && '
                        f'sleep 5 && systemctl restart veyon.service\\\" | at now"')
            self.progress_signal.emit(f'Команда отправлена на {host.name}.')

        self.finish_signal.emit('Отправка команды удалённого входа завершена.')


class EndSession(QThread):
    start_signal = pyqtSignal(str)
    progress_signal = pyqtSignal(str)
    finish_signal = pyqtSignal(str)

    def __init__(self, complete_end_session=False):
        super().__init__()
        self.host_list = None
        self.complete_end_session = complete_end_session

    def run(self) -> None:
        """
        Выполняет удалённый выход из системы на выбранных хостах
        """
        hosts_count = len(self.host_list)
        self.start_signal.emit(
            f"Выполняется удалённый выход из системы на {hosts_count} компьютеров...")
        for host in self.host_list:
            if not test_ssh(host):
                self.progress_signal.emit(f'На {host.name} не настроен ssh.')
                continue
            self.progress_signal.emit(f'Удалённый выход из системы на {host.name}...')
            if self.complete_end_session:
                cmd = """<< 'ENDSSH'
                for sessionid in $(loginctl list-sessions --no-legend | awk '{ print $1 }')                                                                                                          
                do [ "$(loginctl show-session $sessionid --property=Remote --value)" == "no" ] && loginctl kill-session --signal KILL $sessionid
                done
                ENDSSH
                """
                run_command(f'ssh -T root@{host.hostname} {cmd}')
            else:
                run_command(f'ssh root@{host.hostname} '
                            f'"qdbus --system org.freedesktop.DisplayManager /org/freedesktop/DisplayManager/Seat0 '
                            f'org.freedesktop.DisplayManager.Seat.SwitchToGreeter"')
            self.progress_signal.emit(f'Команда отправлена на {host.name}.')

        self.finish_signal.emit('Отправка команды удалённого выхода из системы завершена.')
